Lifetime of borrowed values in struct


#1

Why does this code work

struct Foo<'a> {
    x: &'a i32,
}

impl<'a> Foo<'a> {
    fn get(&self) -> &'a i32 {
        self.x
    }
}    

but the next code doesn’t:

struct Foo<'a> {
    x: &'a mut i32,
}

impl<'a> Foo<'a> {
    fn get(&mut self) -> &'a mut i32 {
        self.x
    }
}

#2

This dose fn get(&mut self) -> &'a mut i32 {

get(&self) says that you can call get on a shared (i.e. immutable) reference to self.
-> &'a mut i32 says that you return a unique (i.e. mutable) reference to an i32.
You cannot turn a shared reference into a unique reference.

So instead we say:
get(&mut self) says that you can call get on a unique (i.e. mutable) reference to self.

Does that make sense?


#3

In addition to what @Eh2406 said, the other issue is annotating the returned reference with lifetime 'a but using an implicit lifetime for &self. Rust will complain that the returned reference outlives the borrow (i.e. Foo itself). So you would just drop 'a from the return value so that lifetime elision will use same lifetime for self and the returned reference (alternatively, use &'a mut self).


#4

Sorry, I made a mistake when posting my second example above by omitting the mut in get(&mut self). With this problem fixed, the compiler complains:

error[E0312]: lifetime of reference outlives lifetime of borrowed content…

just as vitalyd said. But why does the first example above compile? The only difference between the first and second example is the mutability of the value returned by Foo::get().


#5

Mutable references are treated more strictly with regards to reborrowing of inner references. For example, with shared references, if you have &'a &'b Foo, it’s legal to deref through the &'a and reborrow the inner reference with lifetime 'b and keep it even after the outer 'a reference expires. This is because the inner reference is guaranteed to be valid for lifetime 'b, so it doesn’t matter that the outer reference expires early.

However, this is NOT true for mutable references. If you have &'a mut &'b mut Foo, you can only reborrow the inner reference with a lifetime no longer than 'a. This is because a mutable reference is a promise of exclusive access, and if you could reach through one and reborrow the inner mutable reference with a lifetime longer than the outer one, then it would be possible to reach the inner data in more than one way.

I doubt I explained that very well, but I can’t remember where in the docs this particular distinction is described.


#6

I think that you are referring to type variance, that &'a mut T is invariant over T. Am I right?

But your explanation failed to explain why the following code doesn’t compile:

struct Foo<'a> {
    foo: &'a mut i32,
}

impl<'a> Foo<'a> {
    fn get(&self) -> &'a i32 {
        self.foo
    }
}    

The error is still:

error[E0312]: lifetime of reference outlives lifetime of borrowed content...

Here, Foo::get() returns only a shared reference, so according to your reasoning, the program should compile. Did I misunderstand your explanation? Thanks.


#7

When you have a “&'a mut self” borrow, it indicates that you have an exclusive/unique borrow of self for lifetime 'a - this can be the only such mutable borrow during that lifetime. This means that you can return a mutable reference to an inner part of self only for 'a - if you were to allow it for some other lifetime 'b that outlives 'a then it would violate Rust’s principle of unique/exclusive borrow - once 'a ends, the exclusive borrow ends and so you cannot return a mut reference that outlives 'a because no caller is allowed to hold that mut reference beyond the “original” mut borrow of “self”.

With immutable/shared references, it’s totally fine to return a borrowed immutable reference beyond the self’s borrow of the same reference; as long as the reference itself is still valid (its lifetime, rather than self’s lifetime, is still active) it’s ok to give out another shared reference.

So yes, mutable borrows are more restricted in this sense than immutable ones.

Does that help?


#8

Also, to prevent any confusion, a “struct Foo<'a>” declaration defines the lifetime 'a of any borrowed content inside Foo, not of any particular Foo instance’s lifetime. That’s why when you say “get(&mut self) -> &'a i32” it’s saying it returns the reference with its original lifetime, not the lifetime of &mut self - 'a is going to be longer than &self here given Foo<'a>. That’s where Rust is telling you that reference outlives borrowed content.


#9

Now I understand what you are saying. Thanks.

On the other hand, since a struct s cannot hand out a mutable reference r that outlives s, why do we need to specify the lifetime of mutable references inside a struct? For example,

struct Foo<'a> {
    x: &'a mut i32,
}

Isn’t the lifetime 'a unnecessary? Can the compiler simply equate 'a with the lifetime of the struct instance itself?


#10

The lifetime 'a is actually longer than any instance of the struct ('static aside I suppose). Also, the lifetime is specified for any reference, not just mutable ones. I suppose the explicit lifetime could be elided in a simple case like this, but you may have multiple references with different lifetimes as well and then you can’t elide. I’m guessing Rust designers chose not to elide here to be more explicit and also since the ergonomics aren’t really hurt by it, it’s not a big deal? Maybe someone from the core team can shed some light.


#11

This is not true. 'a is a generic parameter (a lifetime parameter specifically). The struct stores a mutable reference whose scope is not determined by the struct definition (because the struct is generic, it can support many different “values” for the 'a parameter).

One thing do all the legal scopes for the parameter 'a have in common: They are longer or equal to the scope of the particular Foo<'x> itself. So yes, we can store a reference whose scope is larger than the scope of the thing we are storing it in.

To give a concrete example, the iterator std::slice::Iter<'x, T> stores a metaphorical reference to T and can give out references &'x T that outlive the existence of the iterator itself. Example code (playground):

let v = vec![1, 2, 3];
let r = {
    v.iter().next().unwrap() // Put a reference to the first element in `r`.
};
// the iterator is no more, but the reference into the vector is still there
println!("The first element is {}", r);

#12

I suppose the explicit lifetime could be elided in a simple case like this, but you may have multiple references with different lifetimes as well and then you can’t elide.

But, as you said, a struct cannot hand out mutable references that outlive the struct, so what benefits do we get for allowing different lifetimes for different mutable references? It seems to me that we won’t lose anything by requiring all mutable references in a struct to have the same lifetime as that of the struct instance.

Did I miss anything?


#13

@bluss I agree with you, but, if I’m not mistaken, you were talking about shared references. Can you think of an example that necessitates generic lifetime parameters for mutable references inside a struct, i.e., where the compiler needs to know that a mutable references inside a struct outlives the struct instance?


#14

Ah, I was talking about lifetimes of references inside a struct in general, not mutable ones specifically. My hunch is that special casing (i.e. providing elision) this particular case isn’t worth it, but perhaps there’s a technical reason behind it as well.


#15

Sure a reference that outlives its holder is possible with mutable references too. An example with Option<&'a mut i32> as the field is simplest, because then the reference can be given away by ownership semantics (Option::take method).

You’re right that borrowing rules do differ between mutable and shared references, I was trying to find where I had just written a comment about that. (Searching, ok I found it.) Here is that comment Returning iterators and lifetime annotation woes

To understand that comment for this case, remember that the method takes &self. That’s the outermost layer of references. The inner one is the struct field self.x.


#16

Excellent explanation. Let me summarize how I understand the rules.

  1. Via &self or &mut self, you can retrieve an inner mutable reference but the lifetime of the acquired reference is at most that of its holder.
  • Via self, you can retrieve an inner mutable reference with its original lifetime.
  • Via &self, &mut self, or self, you can retrieve an inner shared reference with its original lifetime.

Are these rules accurate?

The following program illustrates the 2nd rule:

struct Foo<'a> {
    x: &'a mut i32,
}

impl<'a> Foo<'a> {
    // OK
    fn get(self) -> &'a mut i32 {
        self.x
    }
}

#17

I think that’s an accurate summary. In #2, the borrow via self is given up since self is consumed. So it’s retrieval of the reference, but in a kind of different way from the other 2.


#18

The actual rules don’t pertain to self at all, but any reference. You can use the ownership transfer rule (2) even through a mut ref (Vec.remove, option.take).