21 | fn fail<'a, J: 'a + Jacket<'a>>(j: J) {
| -- lifetime `'a` defined here
22 | Pocket(&j);
| ^^
| |
| borrowed value does not live long enough
| requires that `j` is borrowed for `'a`
23 | }
| - `j` dropped here while still borrowed
Haven't I declared that J must have a lifetime of at least 'a? How can it be that j doesn't live long enough to refer to it (and then immediately drop the reference)?
There are two fundamental approaches to ensure that references always point to a live object. Garbage-collected languages extend the validity region of objects until after the last reference is destroyed. Rust instead invalidates references whenever an object moves or is destroyed, and lifetimes are just the mechanism to do this. Your lifetime annotations need to match what the code actually does, or else the compiler will reject your program.
By taking j by value, you're moving it into the function's local scope, which requires it to be either destroyed or moved elsewhere before the function exits. When you take a reference to a local variable, like &j, it will always get a new lifetime that expires when that local is moved or destroyed. This is the only way to keep both the invariant that references are always valid and direct programmer control over when and where objects are stored and destroyed.
I get that &j gets a new lifetime which expires when j is moved or destroyed, but what does it mean that J: 'a then? Also, why does the following compile, if that's the case?
If I remove the lifetime from the trait, I still have the same lifetime bound on the reference in the struct, so why doesn't it complain that j doesn't live long enough anymore? In fact, it compiles with or without the J: 'a if there's no trait bound.
Lifetime parameters on traits are invariant. Lifetime parameters on structs depend on how the lifetimes are used (within the definition itself). So when you have a lifetime on your trait, and then put a trait bound on your struct with that lifetime, your struct becomes invariant.
When you remove the lifetime parameter from the trait, the only lifetime is introduced by the struct, and there's no lifetime in the bound to make it invariant.
Here's another alternative which compiles. Without the bound, your struct is covariant (like the reference is), and thus a local variable with a shorter lifetime can be created.
Thanks these answers get me pretty far. I'm still a little confused on why the compiler hints me to add J: 'a if I leave it off when I keep the trait bound, and what J: 'a actually means in this context, given that this also compiles:
J:'a generally means that all references stored inside the type J will be valid for at least the lifetime 'a, but it doesn’t tell you anything about how long any particular instance of J will stick around.
The definition of Pocket says that you can only store a &'a Jacket<'a> where the lifetimes match, and the signature of fail says you're getting a Jacket<'a>, so when you make the local Pocket you must be constructing a Pocket(&'a Jacket<'a>).
But no where is there anything that says J: 'a. Maybe J doesn't live for 'a but still implements Jacket<'a>.
Let's try a more concrete example. Consider this:
impl<'a> Jacket<'static> for &'a () {}
fn main() {
let local = ();
fail::<'static, _>(&());
fail::<'static, _>(&local);
}
Without the lifetime bound, neither call to fail errors. And if you then comment out the construction of Pocket, it compiles. There's no restrictions on the lifetime of the parameter, so both calls are okay. But clearly it would be unsound to create a &'static reference to the non-'static data, which is what the construction of Pocket tries to do.
With the lifetime bound, the first call is still ok (the constant () is static and a static ref is produced), but the second call errors (with or without the construction of the Pocket.)