1

I'm trying to build a Virtual Machine in Rust and I'm running into (what I think is) a basic lifetime problem.

Here are the relevant parts of the VM:

#[derive(Copy, Clone)]
enum Value<'v> {
    Bool(bool),
    Str(&'v str),
    Empty,
}

// Updated VM
struct VM<'vm> {
    values: Vec<Value<'vm>>,

    // `VM.strings` will keep references to "result" strings, like those from `concat`.
    strings: Vec<String>,
}

impl<'vm> VM<'vm> {
    // Pushes the given value onto the stack.
    // Returns the index of the value.
    fn push_value(&mut self, value: Value<'vm>) -> usize {
        self.values.push(value);
        return self.values.len() - 1;
    }

    fn get_value(&self, index: usize) -> Value<'vm> {
        // Assume the best of our users...
        return unsafe { *self.values.get_unchecked(index) };
    }

    fn concat(&mut self, i1: usize, i2: usize) -> usize {
        let first = self.get_value(i1);
        let second = self.get_value(i2);
        let result = match (first, second) {
            (Value::Str(v1), Value::Str(v2)) => [v1, v2].concat(),
            _ => panic!("Attempted to CONCAT non-strings."),
        };

        self.strings.push(result);
        let result_ptr = self.strings.last().unwrap();
        self.push_value(Value::Str(result_ptr))
    }
}

I'm trying to write a concat operation for two strings. Here's my attempt:

The Rust compiler is giving me a detailed error that I don't fully understand:

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter in function call due to conflicting requirements
  --> src/lib.rs:38:39
   |
38 |         let result_ptr = self.strings.last().unwrap();
   |                                       ^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 29:5...
  --> src/lib.rs:29:5
   |
29 | /     fn concat(&mut self, i1: usize, i2: usize) -> usize {
30 | |         let first = self.get_value(i1);
31 | |         let second = self.get_value(i2);
32 | |         let result = match (first, second) {
...  |
39 | |         self.push_value(Value::Str(result_ptr))
40 | |     }
   | |_____^
note: ...so that reference does not outlive borrowed content
  --> src/lib.rs:38:26
   |
38 |         let result_ptr = self.strings.last().unwrap();
   |                          ^^^^^^^^^^^^
note: but, the lifetime must be valid for the lifetime `'vm` as defined on the impl at 16:6...
  --> src/lib.rs:16:6
   |
16 | impl<'vm> VM<'vm> {
   |      ^^^
note: ...so that the types are compatible
  --> src/lib.rs:39:14
   |
39 |         self.push_value(Value::Str(result_ptr))
   |              ^^^^^^^^^^
   = note: expected  `&mut VM<'_>`
              found  `&mut VM<'vm>`

Some miscellaneous thoughts:

  • The error seems to be related to the lifetime of the last() pointer. I think I understand this: because you can drop elements from a Vec, the last() pointer is only valid for a short time (within your function). I know that I won't remove elements from VM.strings: is there a way of telling the compiler this?
  • I've considered changing the enum variant to Str(String), but then Value will no longer Copy, which I think is an important trait for register values. Also, because I will not mutate the String value, it feels like &str is the more "correct" choice here?
Stargateur
  • 24,473
  • 8
  • 65
  • 91
  • 2
    *Adding* elements to `VM.strings` is also a liability for long-lived pointers because extending the `Vec` may cause it to reallocate, which will invalidate any extant references. – trent May 16 '20 at 13:26
  • @trentcl string buffer's location won't change unless the string is modified – ekim boran May 16 '20 at 14:08
  • 1
    If you are sure you won't modify the concatted string and remove elements from strings vector you can cast it to raw pointer and back to make compiler happy. But I strongly encourage you to avoid it. – ekim boran May 16 '20 at 14:12
  • 2
    Oh, @boran is right. So you need to prove that none of the `String`s is dropped or modified, and lifetimes alone aren't enough. The accepted answer to the question pretzelhammer linked also mentions this case (ctrl-F "overzealous") – trent May 16 '20 at 14:15
  • @Stargateur maybe i did not understand. resizing the strings vector don't have any effect on strings added to it, so references to their buffers won't be effected is it not the case? – ekim boran May 16 '20 at 14:15

0 Answers0