Why do I get "does not necessarily outlive the lifetime" error when changing to returning mutable reference from immutable reference?

Hi. Please check the following code.

use std::rc::Rc;

struct Meta<'code> {
    script: &'code str
}

struct AstNode<'code> {
    meta: Meta<'code>,
    some_data: u32,
}

struct RuntimeObject<'code> {
    ast: Rc<AstNode<'code>>,
    some_runtime_data: u32,
}

trait SuperRunner {
    fn get_runtime_obj(&mut self) -> &mut RuntimeObject;
    
    fn do_something(&mut self) {
        let _a = self.get_runtime_obj();
        // Doing something
    }
}

struct RuntimeEngine<'code> {
    obj: RuntimeObject<'code>,
    something_else: u32,
}
impl<'code> SuperRunner for RuntimeEngine<'code> {
    fn get_runtime_obj(&mut self) -> &mut RuntimeObject {
        &mut self.obj
    }
}

fn main() {
    
}

This fails compilation with following error.

error[E0308]: mismatched types
  --> src/main.rs:32:9
   |
32 |         &mut self.obj
   |         ^^^^^^^^^^^^^ lifetime mismatch
   |
   = note: expected mutable reference `&mut RuntimeObject<'_>`
              found mutable reference `&mut RuntimeObject<'code>`
note: the anonymous lifetime defined on the method body at 31:24...
  --> src/main.rs:31:24
   |
31 |     fn get_runtime_obj(&mut self) -> &mut RuntimeObject {
   |                        ^^^^^^^^^
note: ...does not necessarily outlive the lifetime `'code` as defined on the impl at 30:6
  --> src/main.rs:30:6
   |
30 | impl<'code> SuperRunner for RuntimeEngine<'code> {
   |      ^^^^^

I am trying to create a JS code interpreter. The script field in the Meta type is supposed to contain a reference to string slice of the script code which is being parsed and interpreted. So that referenced script is supposed to outlive all the structs' objects we see here.

First question is how to fix this error? Second question if I change get_runtime_obj signature to return immutable RuntimeObject (&RuntimeObject) instead then the program compiles fine, why?

You need a 'code generic parameter on SuperRunner to specify the lifetime of the returned RuntimeObject

use std::rc::Rc;

struct Meta<'code> {
    script: &'code str,
}

struct AstNode<'code> {
    meta: Meta<'code>,
    some_data: u32,
}

struct RuntimeObject<'code> {
    ast: Rc<AstNode<'code>>,
    some_runtime_data: u32,
}

trait SuperRunner<'code> {
    fn get_runtime_obj(&mut self) -> &mut RuntimeObject<'code>;

    fn do_something(&mut self) {
        let _a = self.get_runtime_obj();
        // Doing something
    }
}

struct RuntimeEngine<'code> {
    obj: RuntimeObject<'code>,
    something_else: u32,
}
impl<'code> SuperRunner<'code> for RuntimeEngine<'code> {
    fn get_runtime_obj(&mut self) -> &mut RuntimeObject<'code> {
        &mut self.obj
    }
}

The reason your code doesn't compile is that get_runtime_obj currently has a signature like this:

fn get_runtime_obj<'a>(&'a mut self) -> &'a mut RuntimeObject<'a>

While you're trying to return a &'a mut RuntimeObject<'code>.

You may be wondering why this is a problem, 'code should be longer than 'a, right?. That however is not enough. You're not only promising that the RuntimeObject you return will be valid for 'a, but also that whoever gets that reference can put a RuntimeObject<'a> into it. But you expect it to still have a RuntimeObject<'code> inside RuntimeEngine<'code>!

More formally &mut T is invariant with respect to T, and as such you can't turn a &mut T<'a> into a &mut T<'b> even if 'a: 'b.

2 Likes

This is because &T is covariant in T, unlike &mut T which as @SkiFire13 said is invariant in T. That means that &RuntimeObject<'code> is a subtype of &RuntimeObject<'a> whenever 'code is longer-than-or-equal-to 'a (written 'code: 'a), so you can return a value of type &RuntimeObject<'code> from a function whose stated return type is &RuntimeObject<'a>.

1 Like