My first thought is that you may be confusing value liveness scope with lifetime validity bound on a type. But, I'm not actually sure that's the case. Why do you think the lifetime of the type of long
is longer than the borrow of short
though? There's nothing in the code enforcing that constraint.
Anyway, let's look at this like a compiler might. The code works because the compiler could infer lifetimes that met all the implicit and explicit constraints.
fn main() {
// Let's say this is a CovarLifetime<'?0>
// The lifetime to use is going to be inferred
let long = CovarLifeTime { marker: PhantomData };
let short = String::from("abc");
// Let's say this is a &'?1 String
// The lifetime to use is going to be inferred
let borrow = &short;
// Let's say this is CovarLifeTime::<'?2>::method_call::<&'?3 String>
// And again, `'?_` lifetimes are going to be inferred
long.method_call(borrow);
}
And we have
-
'?2
and '?3
have to be valid on the last line (but no where afterwards)
- We must have
'?0: '?2
so the long
is still valid on the last line
- We must have
'?1: '?3
so the borrow
is still valid on the last line
- We must have
&'?3 String: '?2
as per the method constraint
- So we must have
'?3: '?2
, and transitively, '?1: '?2
And I believe that's it:
Any lifetime that meets these constraints is fine. One simple choice would just be to pick the same lifetime for everything! Some lifetime that ends immediately after the last line.
What about if your struct is invariant? Then we must have ?'0 == ?'2
.
-
'?0 : ?'0
(always true)
-
'?1 : '?3
This is still satisfiable, and just making all the lifetimes the same still works. So the variance of the struct doesn't matter for your OP.
Let's look at why it matters in @semicoleon's last playground.
// n.b. the original had `'a: 'b` which implies `'a == 'b`
// I've assumed that was a mistake and removed it
// There is some related conversation further down
fn short_fn<'a, 'b>(long: &'a CovarLifeTime<'b>) {
let short = String::from("abc");
let borrow = &short; // '?1
// CovarLifeTime::<'?2>::method_call::<&'?3 String>
long.method_call(borrow);
}
No matter what:
- '
b: 'a
(implied by the existence of a &'a CovarLifeTime<'b>
)
-
'a
may be strictly less than 'b
though, as the function says they can be different
-
'b
and 'a
outlive the function (as they are lifetime parameters from the caller)
-
'?1
and '?3
cannot outlive the function as they reference a local variable
- Therefore
'?1 : 'b
and '?1 : 'a
and '?3: 'b
and '?3: 'a
can never be satisfied
With covariance:
-
'a: '?2
so the method is well-formed
-
'?1: '?3
so the borrow is well-formed at the method call
-
'?3: '?2
by the method constraint
And all together:
Inferring a lifetime that ends at the last line of the function and using that for all inferred lifetimes still works. How about invariance? That means that '?2 == 'b
, as 'b
is the invariant lifetime.
-
'b : 'a
-
'a : 'b
Can't be satisfied because we said they didn't have to be equal
'b : 'b
-
'?1 : '?3
-
'?3 : 'b
Can never be satisfied because '?3
is local and 'b
isn't
-
'?1 : 'b
Can never be satisfied because '?1
is local and 'b
isn't
That 'a: 'b
point means that even this won't work:
// Actually invariant
fn short_fn<'a, 'b>(long: &'a CovarLifeTime<'b>) {
long.method_call("");
}
However this does:
// Actually invariant
// Equivalent to the original `short_fn<'a: 'b, 'b>(&'a ... <'b>)`
fn short_fn<'b>(long: &'b CovarLifeTime<'b>) {
long.method_call("");
}
But this will still have the "can't borrow a local for longer than the function body" problems.
// Actually invariant
fn short_fn<'b>(long: &'b CovarLifeTime<'b>) {
let short = String::new();
long.method_call(&short);
}
So there's two things making the invariant case problematic:
- We can't coerce
&'a S<'b>
to a &'a S<'a>
- This is a problem with the function signature -- we can never call the method that requires the lifetimes to be equal
- So the only useful signature is to take a
&'a S<'a>
- We can't coerce a
&'a S<'a>
to a &'local S<'local>
- This means we can't pass in local borrows to the method, even with a somewhat useful signature
They aren't a problem in main
because all the lifetimes are free to be inferred, not dictated by the function signature.