First of all, and before I forget, thanks for posting the Cargo.toml
[dependencies]
, which makes reproducing the error a piece of cake.
The theory as to what is Rust complaining about
Let's first consider a lifetime parameter: take, for instance,
fn foo<'a> (s: &'a str)
{
...
}
For how long is s
borrowed? We cannot really know, since that is something that is chosen by the caller, but no matter how short that borrow is, 'a
is a lifetime that spans over all the function body and beyond / 'a
must end after the function foo
returns.
Now let's imagine a type T
that is generic over some lifetime 'a
: T<'a>
.
-
For the example, we will test two implementations:
-
covariant w.r.t.
'a
struct T<'a> ( &'a (), );
-
invariant w.r.t.
'a
struct T<'a> ( &'a mut &'a (), );
-
And let's study the followng function:
fn foo<'a> (x: T<'a>)
{
fn max_borrow<'a> (_: &'a T<'a>)
{}
max_borrow(&x);
}
-
covariant
T<'a>
compiles fine: Playground -
invariant
T<'a>
, on the other hand, yields the compilation error you have been having:error[E0597]: `x` does not live long enough --> src/lib.rs:10:16 | 5 | fn foo<'a> (x: T<'a>) | -- lifetime `'a` defined here ... 10 | max_borrow(&x); | -----------^^- | | | | | borrowed value does not live long enough | argument requires that `x` is borrowed for `'a` 11 | } | - `x` dropped here while still borrowed
This happens because we want to borrow a Foo<'a>
for the whole lifetime 'a
; we usually kind of assume that it means wanting to borrow the Foo<'a>
for its whole own lifetime (as a binding), i.e., borrowing the Foo<'a>
until it dies / is dropped.
It turns out that this is not always the case: as I said at the beginning of the post, the generic lifetime of a function is assumed to last beyond the body of the called function (for obvious soundness reasons). Thus, when trying to borrow it for the lifetime 'a
(in max_borrow
), we were actually trying to hold the borrow beyond the end of the function. But since the function takes ownership of its Foo<'a>
parameter, it becomes local to the function and is thus dropped before the function returns. Hence the error.
The covariant case (the most usual one in practice), dodges this issue by shrinking (thanks to covariance) Foo<'a>
's lifetime into some inner anonymous lifetime that does thus not outlive the function, hence making the borrow on its own not need to be held that long, thus making it doable.
impl Trait<'a>
(anddyn Trait<'a>
) happen to conservatively be always invariant in'a
.
Solution (in practice)
To solve this issue, as with many lifetimes issues, one does not really need to understand them exactly: suffices to spot &'a Foo<'a>
as a "code smell" / potentially problematic pattern.
What the programmer most probably intends, more often than not, is that for some type Self
(which can be Self = Foo<'a>
), we just want to borrow Self
, with no special constraint on the duration of this borrow.
Using &'a self = self: &'a Self = self: &'a Foo<'a>
does not express that "I don't mind how long the borrow is" intent. On the contrary, it is telling Rust "I want the borrow to last exactly 'a
, where 'a
is that lifetime parameter in Foo
. Instead,
one must use
&self = &'_ self = self: &'_ Self
, where'_
is an elided and thus "free" generic lifetime parameter.
For instance,
trait Guy<'a> {
fn get_text (self: &'a Self)
-> &'a str;
}
becomes
trait Guy<'a> {
fn get_text (self: &'_ Self)
-> &'_ str;
}
which can be simplified into not having this unused lifetime parameter:
trait Guy {
fn get_text (self: &'_ Self)
-> &'_ str;
}
and funnily enough we can always name our free lifetime parameter, as long as it is available. In this case, the name 'a
is now available:
trait Guy {
fn get_text<'a> (self: &'a Self)
-> &'a str;
}
So, at the end of the day, we have just moved the genericity from the trait itself to the trait's associated function.
Result
struct My<'text> {
text: &'text str,
}
trait Guy {
fn get_text (self: &'_ Self)
-> &'_ str;
}
impl<'text> Guy for My<'text> {
fn get_text (self: &'_ Self)
-> &'_ str
{
self.text
}
}
async fn hello (my: impl Guy)
{
println!("{}", my.get_text());
}
#[::tokio::main]
async fn main ()
{
let my = My { text: "a" };
hello(my).await;
}