I don't think I agree with the explanation.
You don't need variance to explain the 3-parameter version. There are just three different lifetimes, with explicit "outlives" relationships: the compiler can choose 'x
and 'y
to be the "exact" lifetimes of the parameters, and choose 'a
to be the "exact" lifetime of the result. Variance doesn't need to play in here because all the lifetimes are exact and the outlives-relationships between them are explicit.
(In reality this is not exactly true; there really is no such thing as an "exact" lifetime because the compiler can choose the lifetime of any reference to be as big or small as necessary. But the point remains that you don't need variance to explain the three-lifetime version of longer
, at least for simple uses.)
Here's an example I used in a recent thread on almost the exact same question:
let mut s1 = String::from("foo");
let x: &str = &s1;
println!("{}", x);
let s2 = String::from("quux");
let y: &str = &s2;
let _l = longest(x, y);
s1.clear();
println!("{}", y);
x
and y
are references that must have different lifetimes. But longest(x, y)
works either with the one-lifetime version or the three-lifetime version (or the two-lifetime version from the other thread). How come? The three-lifetime version can just say:
-
'x
is the lifetime of x
-
'y
is the lifetime of y
-
'a
is the lifetime of _l
- All the constraints (
'x: 'a, 'y: 'a
) are satisfied, so we're done.
But the one-lifetime version needs to say:
-
'a
is the lifetime of _l
, which is shorter than the lifetimes of x
or y
- But it's OK to pass
x
to a formal parameter of a shorter lifetime because &'a T
is covariant in 'a
- And it's OK to pass
y
to a formal parameter of a shorter lifetime for the same reason.
Long story short, there's no difference between these three annotations:
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str
fn longest<'a, 'b: 'a>(x: &'a str, y: &'b str) -> &'a str
fn longest<'a, 'x: 'a, 'y: 'a> (x: &'x str, y: &'y str) -> &'a str
They mean the same thing and expose the same API. This is true of both mutable (&'a mut T
) and shared (&'a T
) references because both are covariant in 'a
, but there can be a difference with other types, because not every type is covariant in its lifetime parameters. Consider:
fn longest<'a, 'x: 'a, 'y: 'a>(x: Foo<'x>, y: Foo<'y>) -> Foo<'a>
fn longest<'a>(x: Foo<'a>, y: Foo<'a>) -> Foo<'a>
You'd have to know what kind of variance Foo<'a>
has in 'a
to know whether these two are equivalent or not.