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
'long <: 'short => &'long T <: &'short T
And as a result, we can coerce a
&long T into its supertype
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
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 strictly outlives
'short is UB as it will "lifetime transmute"
'short to something longer. (
foo4 is a concrete example of such a choice,
If you replace both
'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.