Unexplainable lifetime related error

I am facing an issue related to lifetimes that I cannot understand; could someone please help me understand the reason for it?

Following test code fails to compile due to multiple mutable borrow errors.

I understand that 'a lifetime is not required for the mutable reference in (s: &'a mut Foo<'a>) in the method signature; and if I remove that lifetime , then the code compiles fine.

I am trying to understand what is the explanation for the given code snippet to fail compilation in its current form. FWIU, I am not holding on to any extra reference to the struct once the mutate() method returns.

    #[test]
    fn test() {
        struct Foo<'a> {
            r: &'a i32,
        }

        fn mutate<'a>(s: &'a mut Foo<'a>, r: &'a i32) {
            s.r = r;
        }

        let mut s = Foo { r: &10 };
        let r = 10;
        mutate(&mut s, &r);
        mutate(&mut s, &r);  // <-- Error happens here
    }
error[E0499]: cannot borrow `s` as mutable more than once at a time
  --> src/experiments/lifetime.rs:53:16
   |
52 |         mutate(&mut s, &r);
   |                ------ first mutable borrow occurs here
53 |         mutate(&mut s, &r);
   |                ^^^^^^
   |                |
   |                second mutable borrow occurs here
   |                first borrow later used here

&'a T<'a> is almost always wrong (in the sense that it is unusable).

Just like regular, type-level generic parameters, lifetimes are identified by their name within a generic type, so &'a Foo<'a> means that "give me a pointer to Foo with the same lifetime as the pointers inside Foo".

This does not make much sense, since when the borrow checker tries to equate the two lifetimes, it necessarily has to tie the lifetime of the outer borrow to that of the inner one. In your example, this is the lifetime in &10, which is at least as long as the block in which 10 is defined lives (but due to special rules around constants and literals, Rust can even choose it to be 'static).

This in turn means that the outer (mutable) borrow also lasts as long as the entire block, hence, you can't repeat such a mutable borrow within the same block.

All-in-all, references in Rust are supposed to be temporary views, but the &'a Foo<'a> antipattern essentially strips them of this property, rendering the construct largely useless.

If you ever encounter the need to express such potentially related outer and inner lifetimes, use &'a Foo<'b>, possibly with the additional constraint that 'b: 'a if necessary.

As a fun real-world exercise, you can try to implement std::io::Write for a newtype wrapping a &mut std::fmt::Formatter.

2 Likes

Actually what's not usable is &'a mut T<'a> since &mut T is invariant over T. &'a T<'a> is sometimes useful as &'a T is covariant over T, but you need an in-depth understanding of the lifetime system to use it properly.

1 Like