Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't my struct live long enough?

Tags:

rust

lifetime

In Rust, I get the following error:

<anon>:14:9: 14:17 error: `mystruct` does not live long enough
<anon>:14         mystruct.update();
                  ^~~~~~~~
<anon>:10:5: 17:6 note: reference must be valid for the lifetime 'a as defined on the block at 10:4...
<anon>:10     {
<anon>:11         let initial = vec![Box::new(1), Box::new(2)];
<anon>:12         let mystruct = MyStruct { v : initial, p : &arg };
<anon>:13         
<anon>:14         mystruct.update();
<anon>:15         
          ...
<anon>:12:59: 17:6 note: ...but borrowed value is only valid for the block suffix following statement 1 at 12:58
<anon>:12         let mystruct = MyStruct { v : initial, p : &arg };
<anon>:13         
<anon>:14         mystruct.update();
<anon>:15         
<anon>:16         mystruct
<anon>:17     }
error: aborting due to previous error

for the following code:

struct MyStruct<'a>
{
    v : Vec<Box<i32>>,
    p : &'a i32
}

impl<'a> MyStruct<'a>
{
    fn new(arg : &'a i32) -> MyStruct<'a>
    {
        let initial = vec![Box::new(1), Box::new(2)];
        let mystruct = MyStruct { v : initial, p : &arg };

        mystruct.update();

        mystruct
    }

    fn update(&'a mut self)
    {
        self.p = &self.v.last().unwrap();
    }

}

fn main() {
    let x = 5;
    let mut obj = MyStruct::new(&x);
}

(Playground)

I don't understand why mystruct does not live enough. If I comment out the mystruct.update() line it works fine though. What's more is, if I comment out the body of update the code still fails. Why does calling an empty function which borrows a mutable self changes things?

I don't understand which reference is the one the error talks about. Can somebody explain this?

like image 634
loudandclear Avatar asked May 25 '15 09:05

loudandclear


1 Answers

The reference this error talks about is the one which is implicitly created when you call update(). Because update() takes &'a mut self, it means that it accepts a value of type &'a mut MyStruct<'a>. It means that in theory you should call update() like this:

(&mut mystruct).update();

It would be very inconvenient to write this everywhere, and so Rust is able to automatically insert necessary &s, &muts and *s in order to call a method. This is called "autoreference", and the only place it happens is method invocations/field access.

The problem is the definition of update() method:

impl<'a> MyStruct<'a> {
    ...
    fn update(&'a mut self) { ... }
    ...
}

Here you are requesting that update() receives the value it is called at via a reference with lifetime 'a, where 'a is the lifetime of the reference stored in the structure.

However, when you have a structure value you're calling this method on, there should be already a reference to i32 you stored in this structure. Hence the lifetime of the structure value is strictly smaller than the lifetime designated by the lifetime parameter, so it is just impossible to construct &'a mut MyStruct<'a> with local variables (as in your case).

The solution is to use &mut self instead of &'a mut self:

fn update(&mut self) { ... }
// essentially equivalent to
fn update<'b>(&'b mut self) where 'a: 'b { ... }
// `'b` is a fresh local lifetime parameter

This way the lifetime of the structure in this method call is not tied to the reference this structure contains and can be smaller.

More in-depth explanation follows below.

By itself your definition is not nonsense. For example:

struct IntRefWrapper<'a> {
    value: &'a i32
}

static X: i32 = 12345;
static Y: IntRefWrapper<'static> = IntRefWrapper { value: &X };

impl<'a> IntRefWrapper<'a> {
    fn update(&'a self) { ... }
}

Y.update();

Here update() invocation won't cause compilation errors because both lifetimes (of Y and of X, reference to which is contained in Y) are 'static.

Let's consider your example, for comparison:

impl<'a> MyStruct<'a> {
    fn new(arg : &'a i32) -> MyStruct<'a> {
        let initial = vec![Box::new(1), Box::new(2)];
        let mystruct = MyStruct { v : initial, p : &arg };

        mystruct.update();

        mystruct
    }
}

Here we have a lifetime parameter, 'a, which is supplied by the caller of the function. For example, the caller could call this function with a static reference:

static X: i32 = 12345;

MyStruct::new(&X);  // here &X has static lifetime

However, when update() method is invoked, mystruct lifetime is bounded by the block it is called in:

{
    let initial = vec![Box::new(1), Box::new(2)];
    let mystruct = MyStruct { v : initial, p : &arg };  // +
                                                        // |
    mystruct.update();                                  // |
                                                        // |
    mystruct                                            // |
}

Naturally, the borrow checker can't prove that this lifetime is the same as the lifetime provided by the caller (and for any possible "external" lifetime it is indeed impossible for them to match), so it throws an error.

When update is defined like this:

fn update(&mut self) { ... }
// or, equivalently
fn update<'b>(&'b mut self) where 'a: 'b { ... }

then when you call it, it is no longer required that the value you call this method on must live exactly as long as 'a - it is sufficient for it to live for any lifetime which is smaller than or equal to 'a - and the lifetime inside the function perfectly matches these requirements. Thus you can call such method on your value, and the compiler won't complain.

Additionally (as noticed in the comments) the following line is indeed invalid and there is no way around it:

self.p = &self.v.last().unwrap();

The borrow check fails here because you're trying to store a reference with lifetime of the structure into the structure itself. In general this can't be done because it has nasty soundness issues. For example, suppose you were indeed able to store this reference into the structure. But now you can't mutate Vec<Box<i32>> in the structure because it may destroy an element which the previously stored references points at, making the code memory unsafe.

It is impossible to check for such things statically, and so it is disallowed on the borrow checking level. In fact, it is just a nice consequence of general borrow checking rules.

like image 114
Vladimir Matveev Avatar answered Oct 23 '22 08:10

Vladimir Matveev