Named self lifetimes and reborrowing


#1

While adding explicit lifetimes to some code (to help figure out why the borrow checker was complaining about the elided version), I accidentally ran across an example of something I can’t explain to myself. Can anyone explain why the following code has an error?

struct Foo<'a>(&'a i32);

impl<'a> Foo<'a> {
    fn do_one(&'a mut self) {}
    fn do_two(&'a mut self) {}
    
    fn do_all(&'a mut self) {
        self.do_one();
        self.do_two();
    }
}

This results in the error cannot borrow *self as mutable more than once at a time, for the calls to do_one and do_two inside do_all; but similar code without the explicit 'a works, for example:

struct Foo<'a>(&'a i32);

impl<'a> Foo<'a> {
    fn do_one(&mut self) {}
    fn do_two(&mut self) {}
    
    fn do_all(&mut self) {
        self.do_one();
        self.do_two();
    }
}

#2

I guess it’s somehow related to mutable borrow being invariant over lifetime. Simply saying, you can’t reuse same lifetime when reborrowing. Your second snippet looks in fact like this:

struct Foo<'a>(&'a i32);

impl<'a> Foo<'a> {
    fn do_one<'x>(&'x mut self) {}
    fn do_two<'y>(&'y mut self) {}
    fn do_all<'z>(&'z mut self) {
        self.do_one();
        self.do_two();
    }
}

Please note self lifetimes independent of 'a on self references.


#3

Nothing good ever comes from adding lifetime to self.

Foo<'a> means it contains a reference inside, and for that to be valid, the reference must live longer than Foo (so that it never becomes invalid while Foo is using it). When you say self has the same lifetime, you say that Foo lives longer than itself.


#4

It can certainly lead to confusion (at least for me)! Is there a lint that can be turned on to warn about it?

Hmm … from reading various things about lifetimes and references (including the subtyping section in the Nomicon), I thought that the reference inside a Foo<'a> only needed to live at least as long as Foo, so that giving self the same lifetime would just mean that Foo lives as least as long as itself. (EDIT: after reading that section in the Nomicon again, I think I was wrong about that)

And now that I think about it, if the problem really is that &'a mut Foo<'a> implies a Foo that lives longer than itself, how come my first snippet compiles without error if the only change is removing the call to do_two:

impl<'a> Foo<'a> {
    fn do_one(&'a mut self) {}
    fn do_two(&'a mut self) {}
    
    fn do_all(&'a mut self) {
        self.do_one();
    }
}

And if that works, I can’t figure out why this also fails with cannot borrow *self as mutable more than once at a time:

struct Foo<'a>(&'a i32);

impl<'a> Foo<'a> {
    fn do_all(&'a mut self) {
        {
            let x: &'a mut Foo = self;
        }
        let y: &'a mut Foo = self;
    }
}

#5

The “at least as long” is technically true, but there’s always some evaluation and destruction order, so no two things have exactly the same lifetime. With & references the compiler can ignore the difference (lifetime subtyping), but with &mut it’s super picky about then (invariant lifetimes).


#6

In your example you think you’re borrowing for lifetime of {..}, block, but thanks to lifetime annotations, you’re borrowing for lifetime of 'a.

Adding #![feature(nll)] gives that hint.


#7

Yes, that’s what I used to think! Thanks to your help, I believe I now have a better understanding of lifetimes and borrows – namely, that:

When a reference goes out of scope, its borrow will end if and only if the compiler can “shrink” the lifetime of the borrow (via lifetime subtyping) to match the lifetime of that scope; and the compiler cannot do this in the case of certain invariant lifetimes.

Is that correct?


#8

I think so. There’s also a gotcha of reborrowing: https://bluss.github.io/rust/fun/2015/10/11/stuff-the-identity-function-does/