Puzzle about the lifetime `&'a mut self ` [Solved by code refactor]

I'm writing a lib, and I want to use a generic type as the arg of a testing function (i.e. func1 or func2). And everything is simplified.
I think func1 is the correct one, but I can't figure out why func2 is wrong.

use std::fmt::Debug;

trait SomeTrait<'a> {
    type Item: 'a;
    fn process(&'a mut self) -> Self::Item { self.result() }

    fn result(&'a self) -> Self::Item;
}

#[derive(Debug)]
struct Custom([u8; 10]);
impl<'a> SomeTrait<'a> for Custom {
    type Item = &'a [u8];

    fn result(&'a self) -> Self::Item { &self.0 }
}

fn main() {
    let mut arg = Custom([0; 10]);
    func1(&mut arg);
    func2(arg);
}

// Here is obvious:
fn func1<'a, T: SomeTrait<'a>>(arg: &'a mut T)
    where <T as SomeTrait<'a>>::Item: Debug {
    println!("{:?}", arg.process());
}

// But here is what I cannot figure out:
fn func2<'a, T: SomeTrait<'a> + 'a>(mut arg: T)
    where <T as SomeTrait<'a>>::Item: Debug {
    println!("{:?}", arg.process());
} // why will `arg` still be borrowed??
error[E0597]: `arg` does not live long enough
  --> examples/tdx_trait.rs:33:22
   |
31 | fn func2<'a, T: SomeTrait<'a> + 'a>(mut arg: T)
   |          -- lifetime `'a` defined here
32 |     where <T as SomeTrait<'a>>::Item: Debug {
33 |     println!("{:?}", arg.result());
   |                      ^^^---------
   |                      |
   |                      borrowed value does not live long enough
   |                      argument requires that `arg` is borrowed for `'a`
34 | } // why `arg` is still borrowed??
   | - `arg` dropped here while still borrowed

fn process(&'a mut self) makes the lifetime 'a you defined in the generic parameter identical to the lifetime of the mutable borrow.

Two guidelines:

  • &self or &mut self should almost never have a lifetime declared unless it is a lifetime parameter of the function itself — not the trait or type. This is because the lifetime of a borrow of self is usually short, so specifying a longer one is over-constraining.

  • Never use the same lifetime name for an &mut reference as anything else unless there's a specific reason it has to be that. This is because, unlike other lifetimes, mutable lifetimes are invariant: it's not permitted to treat a longer lifetime as a shorter one. Thus, setting them equal tends to cause "still borrowed" errors like you encountered.

    Reusing the same lifetime name with multiple & references can also sometimes be troublesome but it's a lot rarer.

4 Likes

When you have Trait<'a> it means 'a refers to data that outlives the object implementing the trait. Like Vec that outlives vec::Iter.

When you put that on a &'a mut this means exclusive invariant lifetime (i.e. maximally inflexible to the point of being mean).

And combined with self it means the whole object can be used once and only once, because the &mut self call borrows it for maximally long lifetime of the whole trait-implementing object.

1 Like

This is not strictly true (by itself, ignoring the &mut part of the original problem). It would be true of a lifetime parameter on a type, but for a trait, the data only needs to outlive the usage of the object as that trait. For example, in this modification of the original code, the implementation of SomeTrait<'a> returns data owned by self, and yet we can use it repeatedly and even mutate — this is allowed because the two calls to c.result() can have independent lifetimes, both shorter than the life of the object.

trait SomeTrait<'a> {
    type Item: 'a;
    fn result(&'a self) -> Self::Item;
}

impl<'a> SomeTrait<'a> for [u8; 10] {
    type Item = &'a u8;
    fn result(&'a self) -> Self::Item { &self[0] }
}

fn main() {
    let mut c = [7; 10];
    
    dbg!(c.result());
    c[0] = 8;
    dbg!(c.result());
}
3 Likes

Great!
I wish I could read these in the api guidelines book.

I realized the trait or struct declaration may need to refactor.
But how can I define a trait associated type that can be correctly returned?
link to playground
The original code (&'a mut self stuff) I paste is the result that I followed all the advice by the complier. I know the compiler isn't always informative...

Common Rust Lifetime Misconceptions (link to article)

[…]
5) if it compiles then my lifetime annotations are correct
[…]
7) compiler error messages will tell me how to fix my program
[…]

2 Likes

If the output type is always of the form &'a _, you can fix it by simply pulling the &'a out of the associated type, e.g.

trait SomeTrait {
    type Item: ?Sized;
    fn process(&mut self) -> &Self::Item {
        self.result()
    }

    fn result(&self) -> &Self::Item;
}

#[derive(Debug)]
struct Custom {
    data: [u8; 10],
}

impl SomeTrait for Custom {
    type Item = [u8];

    fn result(&self) -> &Self::Item {
        &self.data
    }
}

(I'm not sure why you had <'a> on Custom; it appears to serve no purpose even in the original, so I just removed it)

This has the advantage that you can use Self::Item in different ways: for example, you could also have a result_mut method that returns &mut Self::Item, instead of needing a second associated type ItemMut.

If that does not work, you may be looking for generic associated types or GATs. However, this is only available on nightly at the moment. There are workarounds you may find useful, but if you don't need it, I recommend the simple solution I mentioned earlier.

1 Like

This what I need!

Thank you all!

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.