&'a mut Inner<'b>
is not an anti-pattern. With &'a mut Inner<'b>
, 'a
is able to be shorter than 'b
-- for example as short as a method call. The outer lifetime is often elided and the inner lifetime may be behind an alias like self
.
impl<'b> Inner<'b> {
// Various amounts of elision and aliases
fn foo_1<'a>(inner: &'a mut Inner<'b>) { /* ... */ }
fn foo_2<'a>(inner: &'a mut Self) { /* ... */ }
fn foo_3(inner: &mut Inner<'b>) { /* ... */ }
fn foo_4(inner: &mut Self) { /* ... */ }
// Same but for a method taking `&mut self`
fn bar_1<'a>(&'a mut self) { /* ... */ }
fn bar_2(&mut self) { /* ... */ }
The most common of these is probably &mut self
, where you don't even really think about it. In all cases, eliding the lifetime gets you a distinct one.
&'a Inner<'a>
can sometimes be less flexible than &'a Inner<'b>
, but it often doesn't matter. If Inner
is invariant over its lifetime parameter, than you may run into similar issues with &'a Inner<'a>
as you do with &'a mut Inner<'a>
, so having separate parameters is the safer bet. And, like with the example above, just not naming the parameter gets you the two lifetimes "for free" anyway.
I say "it often doesn't matter" because if Inner
is covariant over the lifetime parameter, then it can "shrink" automatically where it usually matters -- for example in a method call, you may end up with a &'a Inner<'a>
that only lasts the length of the call, even if you called it on a Inner<'static>
, say.
But even this might still be too inflexible if you actually need to preserve the inner lifetime for some reason though. For example, since shared references are Copy
, you can get (a copy of the) &'static str
reference out from the inside of a &'short &'static str
.
pub fn quz(s: &/* 'short */ &'static str) -> &'static str {
*s
}
So I guess in summary I'd say that &'a Inner<'a>
isn't the red flag that &'a mut Inner<'a>
is, and it's less likely to be a problem because Rust tries hard to find some interpretation that works and that is easier to achieve with covariant lifetimes, but it's often still a questionable choice (for example in a method signature where you could get two lifetimes just via elision). It might be sensible if you're storing a &'a Inner<'a>
in another struct and you want to avoid two lifetime parameters or the like (provided the inner lifetime is covariant and not significant to preserve).