Confusion about the lifetime

In the following example, why does the lifetime affect self ?

struct Foo<'a, T> {
    data: &'a T
}

impl<'a, T> Foo<'a, T> {
    fn get_data(&'a mut self) -> &'a T {
        self.data
    }

    fn get_data_1(&mut self) -> &'a T {
        self.data
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test() {
        let mut f = Foo { data: &10 };
        {
            // this is ok
            let d = f.get_data_1();
            let d2 = f.get_data_1();
        }
        {
            let d = f.get_data();
            // ERROR: cannot borrow `f` as mutable more than once at a time
            //let d2 = f.get_data();
        }
    }
}

(Playground)

In get_data(), the lifetime annotations tie the lifetime of the output to the lifetime of &mut self. Since the input is a mutable reference and the output has the same lifetime, the output falls under the restrictions for mutable references so holding two copies of it is not allowed (and Rust also can't rule out the possibility of you storing the output back into data so it has to assume you are still holding it as long as f exists, and dependency on self sort of makes it self-borrowing).

In get_data_1() nothing ties the output to a mutable reference, it just has the same lifetime as data, which is a shared reference. Since shared references are Copy, there is nothing wrong with holding more than one.

I would say get_data_1() is the correct way to write this. Generally, nothing good comes from over-constraining lifetimes.

In fact, &'a mut Foo<'a> is almost always wrong, since it will essentially lock the structure for the whole span of its existence.

2 Likes

Thx for reply.

In addition, I would like to ask, the relationship and difference each lifetime annotations. Thx. :slight_smile:

struct Foo<'a, T>
impl<'a, T>
fn get_data(&'a mut self) -> &'a T

here the lifetime is just declaring a lifetime parameter for the struct, not much more to say IMO

The impl block, in full being

impl<'a, T> Foo<'a, T> {
    fn get_data(&'a mut self) -> &'a T { .... }
}

Is a way of associating a function/method to Foo but apart from the method syntax and namespacing this allows, you can read impl<params> TypeExpr { ... } as the impl parameters being added to every function inside the block, and Self being introduced as an abbreviation for TypeExpr. As such, we can translate:

impl<'a, T> Foo<'a, T> {
    fn get_data(&'a mut self) -> &'a T { .... }
}

into

impl<'a, T> Foo<'a, T> {
    fn get_data(self: &'a mut Self) -> &'a T { .... }
}

into

fn get_data<'a, T>(self: &'a mut Foo<'a, T>) -> &'a T { .... }

(although the last one is not quite 100% legal Rust syntax anymore since you can’t name a function argument self)

Now for such a function, the parameters are interpreted as being universally quantified, so you read:

For all types T and for all lifetimes 'a, the function get_data can accept an argument of type &'a mut Foo<'a, T> and this function application produces a result of type &'a T.

This restricts the type of what can be passed to get_data to mutable references to Foo where the lifetime of the reference is the same as the lifetime parameter of Foo.

In your example this has the effect that two calls to get_data would need two mutable references with the same lifetime (the lifetime being equal to the lifetime parameter of the Foo), i.e. this would need two mutable references to the Foo value to exist/be alive at the same time.

1 Like

Did you intend this struct to be a temporary view of the data, permanently locked to the scope it's been created in?

It's a very rare case, with lots of complex limitations. In almost every situation it's a novice mistake to use references inside structs.

If you want to store data "by reference", use Box<T>. If you want to have structs that share the same data by reference, use Arc<T> (read-only) or Arc<Mutex<T>> (read-write).

Rust's temporary borrows (&) are not for storing data.

Hi Chee, your particular example is covered in Misconception #5 from Common Rust Lifetime Misconceptions. To quote the article:

If we have some struct generic over 'a we almost never want to write a method with a &'a mut self receiver. What we're communicating to Rust is "this method will mutably borrow the struct for the entirety of the struct's lifetime" . In practice this means Rust's borrow checker will only allow at most one call to some_method before the struct becomes permanently mutably borrowed and thus unusable. The use-cases for this are extremely rare but the code above is very easy for confused beginners to write and it compiles. The fix is to not add unnecessary explicit lifetime annotations and let Rust's lifetime elision rules handle it.

If you're new to Rust and find lifetimes confusing I recommend you read the whole article I think you'll find it very helpful!

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.