In short: foo1
compiles because most types are variant over their generic parameters and the compiler can still chose a Spinner
impl for t
. foo2
doesn't compile because traits are invariant over their generic parameters and the Spinner
impl is already fixed.
Some explanation
Let's take a look at a third version of foo
:
fn foo3<'a>(t: &'a Planet<&'a i32>) {
let x: i32 = 10;
Spinner::<&'a i32>::spin(t, &x);
}
This results in the same error as your foo2
. What's going in there?
By writing Spinner::<&'a i32>::spin
, we force the compiler to use a specific implementation of the Spinner
trait. And the signature of Spinner::<&'a i32>::spin
is fn spin(&self, value: &'a i32)
. Period. The lifetime 'a
is given by the caller; foo
can't choose it. Thus we have to pass a reference that lives for at least 'a
. That's why the compiler error happens.
So why does foo1
compile? As a reminder:
fn foo1<'a>(t: &'a Planet<&'a i32>) {
let x: i32 = 10;
t.spin(&x);
}
Here, the lifetime 'a
is also given by the caller and cannot be chosen by foo1
. But, foo1
can chose which impl of Spinner
to use! Note that impl<T> Spinner<T> for Planet<T>
basically defines infinitely many specific implementations (one for each T
). So the compiler also knows that Planet<&'x i32>
does implement Spinner<&'x i32>
(where 'x
is the specific lifetime of x
in the function)!
Now the compiler just has to figure out if it can turn Planet<&'a i32>
into Planet<&'x i32>
. And yes, it can, because most types are variant over their generic parameters and thus Planet<&'a i32>
is a subtype of Planet<&'x i32>
if 'a
is a subtype of 'x
(which it is). So the compiler just "converts" t
to Planet<&'x i32>
and then the Spinner<&'x i32>
impl can be used.
Fantastic! But now to the main part: why doesn't foo2
compile then? Again, as a reminder:
fn foo2<'a>(t: &'a Spinner<&'a i32>) {
let x: i32 = 10;
t.spin(&x);
}
Again, 'a
is given by the caller and foo2
cannot chose it. Unfortunately, now we already have a specific implementation! Namely Spinner<&'a i32>
. We can't just assume that the thing we were passed also implements Spinner<&'o i32>
for any other lifetime 'o != 'a
! Traits are invariant over their generic parameters.
In other words: we know we have something that can handle references which live at least as long as 'a
. But we can't assume that the thing we were handed can also handle lifetimes shorter than 'a
!
As an example:
struct Star;
impl Spinner<&'static i32> for Star {
fn spin(&self, value: &'static i32) {}
}
static SUN: Star = Star;
foo2(&SUN);
In this example, 'a
of foo2
is 'static
. And in fact, Star
implements Spinner
only for 'static
references to i32
.
By the way: this is not specific to trait objects! Let's look at this fourth version of foo
:
fn foo4<'a, S: Spinner<&'a i32>>(t: &'a S) {
let x: i32 = 10;
t.spin(&x);
}
Same error once again. The problem is, again, that the Spinner
impl is already fixed! As with the trait object, we only know that S
implements Spinner<&'a i32>
, not necessarily more.
HRTB to the rescue?
Using higher ranked trait bounds resolves the issue:
fn foo2(t: &for<'a> Spinner<&'a i32>)
and
fn foo4<S: for<'a> Spinner<&'a i32>>(t: &S)
As it's hopefully clear from the explanation above, this works because we the specific impl of Spinner
isn't fixed anymore! Instead, we again have infinitely many impls to choose from (one for each 'a
). Thus we can choose the impl where 'a == 'x
.