Lifetime problem using struct containing reference as generic parameter


#1

I’m using a library that has the following interface (code snippets simplified to relevant details):

use std::marker::PhantomData;

struct SomeLib<T>(PhantomData<T>);

impl<T> SomeLib<T> {
    fn foo(&mut self, t: &mut T) { }
}

Note that the library struct does not need a value of type T to be created.

At first I was using it with a struct that only contained data as the generic parameter and everything went well. The code looked roughly like this: (1)

struct MyStruct1;

fn baz1() {
   let mut lib = SomeLib(PhantomData);
   let mut my1 = MyStruct1;

   lib.foo(&mut my1);
}

As I was fleshing out the code a bit I realised my struct needed to contain a reference to some data. After adding a field for the reference and a lifetime parameter to the struct I had the following code: (2)

struct MyStruct2<'a>(&'a mut u8);

fn baz2() {
   let mut lib = SomeLib(PhantomData);
   let mut data = 0;
   let mut my2 = MyStruct2(&mut data);

   lib.foo(&mut my2);
}

However (2) fails to compile with a message claiming that data does not live long enough:

main.rs:23:33: 23:37 error: `data` does not live long enough
main.rs:23    let mut my2 = MyStruct2(&mut data);
                                           ^~~~
main.rs:21:39: 26:2 note: reference must be valid for the block suffix following statement 0 at 21:38...
main.rs:21    let mut lib = SomeLib(PhantomData);
main.rs:22    let mut data = 0;
main.rs:23    let mut my2 = MyStruct2(&mut data);
main.rs:24
main.rs:25    lib.foo(&mut my2);
main.rs:26 }
main.rs:22:21: 26:2 note: ...but borrowed value is only valid for the block suffix following statement 1 at 22:20
main.rs:22    let mut data = 0;
main.rs:23    let mut my2 = MyStruct2(&mut data);
main.rs:24
main.rs:25    lib.foo(&mut my2);
main.rs:26 }

I need some help to understand why (2) fails.
In (1) foo is called with a reference to a value that lives longer than the function call but shorter than lib, and it works fine.
But in (2) where both my2 and data lives longer than the call to foo and shorter than lib it does not work.
Moving the binding of data to before lib makes the example code compile, but it would be rather awkward to do this in the real code.

Why doesn’t the code in (2) work? Is there some way to change MyStruct2 so that it can contain a reference to data shorter lived than the library struct but still be passed to foo?


#2

It’s all in the types. Here’s what your code looks like with lifetime annotations added:

use std::marker::PhantomData;

struct SomeLib<T>(PhantomData<T>);

impl<T> SomeLib<T> {
    fn foo(&mut self, t: &mut T) { }
}

struct MyStruct2<'a>(&'a mut u8);

fn baz2() {
/*'i:*/ {   let mut lib: SomeLib<MyStruct2</*'j*/>> = SomeLib(PhantomData);
    /*'j:*/ {   let mut data = 0;
        /*'k:*/ {   let mut my2: MyStruct2</*'j*/> = MyStruct2(&/*'j*/mut data);
                    lib.foo(&/*'k*/mut my2);
                }
            }
        }
}

First, note the braces. Every new binding effectively introduces a new scope, due to the way variables are dropped. Specifically, variables are dropped in reverse lexical order. The important part here is that data gets destroyed before lib.

The key is that lib.foo(&mut my2) is used to work out the type of lib. Specifically, it infers it to SomeLib<MyStruct2<'j>>. This means that T is MyStruct<'j>.

This is a problem because it means that lib, which has lifetime 'i, depends on MyStruct2<'j> which absolutely cannot exist beyond lifetime 'j. To put it in other words: lib depends on something with a shorter lifetime than itself. This is very bad.

“But,” you might argue, "SomeLib doesn’t actually contain any pointers!" Ah, but you see, it does. At least, it does as far as the compiler is concerned. PhantomData<T> in this case becomes PhantomData<MyStruct2<'j>> which does contain a pointer.

This is the job of PhantomData: to trick the compiler into thinking that it contains a T without it actually containing a T.

As for why reversing the relative order of data and lib works: well, that’s because doing so means lib now depends on a type with a longer lifetime than itself, which is just fine.

And I can’t think of anything you could do to MyStruct2 to make this code work; anything you did do would, basically be akin to violating memory safety.