How to interpret this "borrowed value does not live long enough"?

I recently spent a lot of time trying to understand the error messages of the following piece of code, but still I don't understand the reason why it fails to compile. Could someone help me?

By removing the mut from the line marked with X (and commenting the line marked with Y), it compiles fine.

I reduced the code as much as I could, from a workspace with 5 crates down to 30 lines... Here it is.

src/main.rs:

extern crate jni; // jni = { version = "0.9.0", features = ["invocation"] }

use jni::*;

struct JavaVMHolder(JavaVM);

impl<'a> JavaVMHolder {
    fn new() -> Self {
        let jvm_args = InitArgsBuilder::new().build().unwrap();
        let jvm = JavaVM::new(jvm_args).unwrap();
        JavaVMHolder(jvm)
    }

    fn attach(&'a mut self) -> AttachGuardHolder<'a> {
        let guard = self.0
            .attach_current_thread()
            .expect("");

        AttachGuardHolder(guard, 0)
    }
}

struct AttachGuardHolder<'a>(AttachGuard<'a>, u32);

impl<'a> AttachGuardHolder<'a> {
    fn foo(&'a mut self) { // <-- line X --------------------
        self.1 += 1;       // <-- line Y --------------------
        println!("Mutated value: {}", self.1);
    }
}

fn main() {
    let mut jvm_holder = JavaVMHolder::new();
    let mut attach_guard_holder = jvm_holder.attach();
    attach_guard_holder.foo();
}

Compiler message

error[E0597]: `attach_guard_holder` does not live long enough
  --> my-crate/src/main.rs:35:5
   |
35 |     attach_guard_holder.foo();
   |     ^^^^^^^^^^^^^^^^^^^ borrowed value does not live long enough
36 | }
   | - `attach_guard_holder` dropped here while still borrowed
   |
   = note: values in a scope are dropped in the opposite order they are created

warning: variable does not need to be mutable
  --> my-crate/src/main.rs:34:9
   |
34 |     let mut attach_guard_holder = jvm_holder.attach();
   |         ---^^^^^^^^^^^^^^^^^^^^
   |         |
   |         help: remove this `mut`
   |
   = note: #[warn(unused_mut)] on by default

error: aborting due to previous error

error: Could not compile `my-crate`.

(On mobile so apologies for bad formatting)

AttachGuardHolder has a lifetime parameter that is specified by the caller and which indicates that AttachGuardHolder has a reference to something borrowed from outside (with 'a).

foo() is specified to borrow self for that same lifetime. This essentially borrows AttachGuardHolder for its entire lifetime, which is going to be shorter than 'a.

To complicate matters, it’s borrowing self mutably - this means the lifetime of AttachGuardHolder has to be exactly the same as 'a because mutable lifetimes become invariant. Since AttachGuardHolder is dropped before jvm_holder, the compiler complains.

If you remove the mutable borrow, lifetimes are allowed to be variant. As such, you can borrow self for a longer lifetime (there’s no harm since it’s immutable).

The fix here is to change &'a mut self to &mut self in foo(). This will use a shorter borrow lifetime, just for that call to foo.

1 Like

Thank you, it's very useful! I didn't know that &'a mut T is invariant over T.
Now that I learned how to name this thing (lifetime invariance), I found a long explanation of it in the documentation of lifetime subtyping. I put the link in case someone else has my same problem.

I'm still thinking about this... I'm trying to remove the dependency on jni by writing my own structs. From my understanding the following code should trigger the same compile-time error. However, it compiles fine. So it means that I still miss something :thinking:

(Link to Rust Playground)

struct MyJavaVM(u32);
struct MyAttachGuard<'a>(&'a MyJavaVM);

impl MyJavaVM {
    fn new() -> Self {
        MyJavaVM(0)
    }

    fn attach(&self) -> MyAttachGuard {
        MyAttachGuard(self)
    }
}

struct JavaVMHolder(MyJavaVM);

impl<'a> JavaVMHolder {
    fn new() -> Self {
        let jvm = MyJavaVM::new();
        JavaVMHolder(jvm)
    }

    fn attach(&'a mut self) -> AttachGuardHolder<'a> {
        let guard = self.0.attach();

        AttachGuardHolder(guard, 0)
    }
}

struct AttachGuardHolder<'a>(MyAttachGuard<'a>, u32);

impl<'a> AttachGuardHolder<'a> {
    fn foo(&'a mut self) { // Here I keep the "wrong" lifetime 'a
        self.1 += 1;
        println!("Mutated value: {}", self.1);
    }
}

fn main() {
    let mut jvm_holder = JavaVMHolder::new();
    let mut attach_guard_holder = jvm_holder.attach();
    attach_guard_holder.foo();
}

Add the following to your playground and see what happens :slight_smile::

impl<'a> Drop for MyAttachGuard<'a> {
    fn drop(&mut self) {}
}

The AttachGuard from the JNI crate you're using has a Drop impl, and that's what makes the code barf there as well. In fact, now that I think about it, there may not be a variance/invariance issue here at all and it boils down to the drop check.

You can read more about drop check here, it's closely related to borrow checking.

Also, somewhat tangentially, even if your code in main() compiled, there's a second issue there - you wouldn't be able to call any more methods on attach_guard_holder after the line that calls foo(). This is because &'a mut self causes the value to be borrowed mutably for essentially its entire lifetime, and no more borrows of any kind would be permitted.

I see, thanks! It is much simpler than I had expected :slight_smile: