Note that we abuse the terminology of "types" to apply to lifetimes as well, even though lifetimes on their own are not types. Putting that aside, we have
'long: 'short => 'long <: 'short
// If 'long outlives 'short, 'long is a subtype of 'short
And we say that &'a T
is covariant in 'a
(and T
), meaning
'long <: 'short => &'long T <: &'short T
And as a result, we can coerce a &long T
into its supertype &'short T
.
More generally, if a type constructor Z(P)
is covariant in its parameter P
, we have
P <: Q => Z(P) <: Z(Q)
That's covariance -- this relationship between the subtyping of the parameters and the subtyping of the type constructor.
However, we have fn(U)
as being contra-variant in U
; that means, fn(&'a T)
is contravariant in 'a
as well, thus:
'long <: 'short => fn(&'short T) <: fn(&'long T)
// General contravariance: P <: Q => Z(Q) <: Z(P)
From which we can coerce a fn(&'short T)
into a fn(&'long T)
.
Are we lengthening any borrow in order to do so? No, fn(U)
is not itself a (type of a) borrow.
After this coercion (in isolation), can we lengthen any borrows by passing them in? No, it just means I can pass in longer-lived borrows to a function that is expecting shorter-lived borrows. The same result could be achieved by shortening the parameter's lifetime. I can no longer pass shorter-lived borrows into the coerced function, because it now demands a longer borrow.
Does this mean references are not always covariant? No, being covariant defines how the subtypes of parameters of a type constructor relate to the subtypes of the outputs of the type constructor. Just because &'a T
being covariant in 'a
can be interpreted as "The 'a
of a &'a T
can only coerce to something shorter" does not mean you can conclude "The 'a
of a fn(&'a T)
can only coerce to something shorter", even though there's a &'a T
in there. Those are two different type constructors. The entire composed type constructor must be taken into account.
What might happen if you could coerce fn(&'static T)
to fn(&'short T)
? Then you could store a reference to a local variable in a global resource by passing it into the previously-&'static T
-expecting function, for example. That is say, it would be unsound.
The transformation is problematic because it breaks the relationship between the two parameters. In foo2
, two of the borrows had to have the same lifetime, and there was an implied bound due to the first parameter:
And that implied bound is required to make the original foo
-- which returns the second parameter -- sound. I.e. the relationship between the two parameters is crucial here.
The coercion to foo3
breaks that relationship -- the two borrows longer have to have the same lifetime. Moreover, the implied bound is now (the trivial):
And any choice of 'x
for 'out
where 'x
strictly outlives 'short
is UB as it will "lifetime transmute" 'short
to something longer. (foo4
is a concrete example of such a choice, 'static
.)
If you replace both 'short
with 'static
, there is no problem .
Another section of the article examines how the breaking of these relationships is problematic even when there is no contravariance at play.