Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Linking the lifetimes of self and a reference in method

Tags:

rust

I have this piece of code:

#[derive(Debug)]
struct Foo<'a> {
    x: &'a i32,
}

impl<'a> Foo<'a> {
    fn set(&mut self, r: &'a i32) {
        self.x = r;
    }
}

fn main() {
    let v = 5;
    let w = 7;
    let mut f = Foo { x: &v };

    println!("f is {:?}", f);

    f.set(&w);

    println!("now f is {:?}", f);
}

My understanding is that in the first borrow of the value of v, the generic lifetime parameter 'a on the struct declaration is filled in with the lifetime of the value of v. This means that the resulting Foo object must not live longer than this 'a lifetime or that the value of v must live at least as long as the Foo object.

In the call to the method set, the lifetime parameter on the impl block is used and the lifetime of the value of w is filled in for 'a in the method signature. &mut self is assigned a different lifetime by the compiler, which is the lifetime of f (the Foo object). If I switched the order of the bindings of w and f in the main function, this would result in an error.

I wondered what would happen if I annotated the &mut self reference with the same lifetime parameter 'a as r in the set method:

impl<'a> Foo<'a> {
    fn set(&'a mut self, r: &'a i32) {
        self.x = r;
    }
}

Which results in the following error:

error[E0502]: cannot borrow `f` as immutable because it is also borrowed as mutable
  --> src/main.rs:21:31
   |
19 |     f.set(&w);
   |     - mutable borrow occurs here
20 | 
21 |     println!("now f is {:?}", f);
   |                               ^ immutable borrow occurs here
22 | }
   | - mutable borrow ends here

In contrast to the example above, f is still considered mutably borrowed by the time the second println! is called, so it cannot be borrowed simultaneously as immutable.

How did this come to be?

By not leaving off the lifetime annotation the compiler filled one in for me for &mut self in the first example. This happens by the rules of lifetime elision. However by explicitly setting it to 'a in the second example I linked the lifetimes of the value of f and the value of w.

Is f considered borrowed by itself somehow?

And if so, what is the scope of the borrow? Is it min(lifetime of f, lifetime of w) -> lifetime of f?

I assume I haven't fully understood the &mut self reference in the function call yet. I mean, the function returns, but f is still considered to be borrowed.

I am trying to fully understand lifetimes. I am primarily looking for corrective feedback on my understanding of the concepts. I am grateful for every bit of advice and further clarification.

like image 870
jtepe Avatar asked May 16 '15 09:05

jtepe


Video Answer


1 Answers

In the call to the method set the lifetime parameter on the impl block is used and the lifetime of the value of w is filled in for 'a in the method signature.

No. The value of the lifetime parameter 'a is fixed at the creation of the Foo struct, and will never change as it is part of its type.

In your case, the compiler actually choses for 'a a value that is compatible with both the lifetimes of v and w. If that was not possible, it would fail, such as in this example:

fn main() {
    let v = 5;
    let mut f = Foo { x: &v };

    println!("f is {:?}", f);
    let w = 7;
    f.set(&w);

    println!("now f is {:?}", f);
}

which outputs:

error[E0597]: `w` does not live long enough
  --> src/main.rs:21:1
   |
18 |     f.set(&w);
   |            - borrow occurs here
...
21 | }
   | ^ `w` dropped here while still borrowed
   |
   = note: values in a scope are dropped in the opposite order they are created

Exactly because the 'a lifetime imposed by v is not compatible with the shorter lifetime of w.

In the second example, by forcing the lifetime of self to be 'a as well, you are tying the mutable borrow to the lifetime 'a as well, and thus the borrow ends when all items of lifetime 'a goes out of scope, namely v and w.

like image 76
Levans Avatar answered Oct 24 '22 23:10

Levans