Why does the compiler accept that a temporary borrow lives long enough in this example?

Consider the following code snippet:

enum Value {
    Int(i32),
    Double(f64),
    Long(i64),
    Null
}

struct VM<'vm> {
    pub stack: Vec<&'vm Value>
}

impl<'vm> VM<'vm> {
    fn push_val(&mut self) {
        self.stack.push(&Value::Null);
    }
}

fn main() {
    let mut vm = VM { stack: vec![] };
    
    vm.push_val();    
}

The code snippet above is a condensed example of some code (real code here) that I absentmindedly wrote the other day and noticed today that ostensibly should not be valid despite Cargo check seeming to find no issue with it.

The issue is that VM.stack is a vector of borrowed enums that must live for at least 'vm, which is the lifetime of the VM itself. However,

    fn push_val(&mut self) {
        self.stack.push(&Value::Null);
    }

is a method that pushes a borrowed Value instance that clearly doesn't live that long? If my understanding is correct (which evidently it isn't), Value::Null, is a temp variable that is immediately borrowed and not anchored anywhere else in the program. So &Value::Null is a borrow that only lives for as long as push() is running. Therefore, &Value::Null does not outlive 'vm and thus does not live long enough to be pushed to VM's vector. Yet, Cargo seems to conclude that it does. What is going on here?

Constant promotion. The Value::Null value is promoted to a static. So this also works:

    let mut vm = VM::<'static> { stack: vec![] };
    vm.push_val();

(As would any other lifetime, thanks to covariance.)

Thank you very much for the answer! That explains everything.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.