Formalizing variance
Some kinds of memory live longer than others. This is captured through the idea
of the outlives relationship. If 'b outlives 'a, it is written as 'b: 'a.
For example, in the definition:
#![allow(dead_code)]
fn main() { }
struct OutlivesExample<'a, 'b: 'a> {
a_str: &'a str,
b_str: &'b str,
}
the borrowed string b_str lives at least as long as a_str, and possibly longer.
The Rust compiler annotates every lifetime parameter with one of three settings.
For a type T<'a>, 'a may be:
-
covariant, which means that if
'b: 'athenT<'b>: T<'a>. This is the default for immutable data. -
invariant, which means that even if
'b: 'a, nothing can be said about the relationship betweenT<'b>andT<'a>. This can happen for one of two reasons:-
If the lifetime is present "inside" some sort of mutable context -- whether a
&mutreference, or interior mutability likeRefCell,OnceCell, orMutex. -
If the lifetime is used in multiple spots where the variances conflict. See Conflicts and type parameters for an example.
-
-
contravariant, which means that if
'b: 'athenT<'a>: T<'b>. This is uncommon and only shows up in parameters tofnpointers.
The variance of a parameter is determined entirely through the type definition. There's no marker trait for this.
Quick exercise
In the struct below, what are the variances of each lifetime parameter?
#![allow(dead_code)]
fn main() {}
use std::cell::Cell;
struct Multi<'a, 'b, 'c, 'd1, 'd2> {
a: &'a str,
b: Cell<&'b str>,
c: fn(&'c str) -> usize,
d: &'d1 mut &'d2 str,
}
fn a<'a, 'b, 'c, 'd1, 'd2>(
x: Multi<'static, 'b, 'c, 'd1, 'd2>
) -> Multi<'a, 'b, 'c, 'd1, 'd2> {
x
}
fn c<'a, 'b, 'c, 'd1, 'd2>(
x: Multi<'a, 'b, 'c, 'd1, 'd2>
) -> Multi<'a, 'b, 'static, 'd1, 'd2> {
x
}
fn d1<'a, 'b, 'c, 'd1, 'd2>(
x: Multi<'a, 'b, 'c, 'static, 'd2>
) -> Multi<'a, 'b, 'c, 'd1, 'd2> {
x
}
The answers
'ais covariant, because it only shows up in an immutable context. This means that, similar to the shortener functions above, you can define a function like:
#![allow(dead_code)] fn main() {} use std::cell::Cell; struct Multi<'a, 'b, 'c, 'd1, 'd2> { a: &'a str, b: Cell<&'b str>, c: fn(&'c str) -> usize, d: &'d1 mut &'d2 str, } fn a<'a, 'b, 'c, 'd1, 'd2>( x: Multi<'static, 'b, 'c, 'd1, 'd2> ) -> Multi<'a, 'b, 'c, 'd1, 'd2> { x } fn c<'a, 'b, 'c, 'd1, 'd2>( x: Multi<'a, 'b, 'c, 'd1, 'd2> ) -> Multi<'a, 'b, 'static, 'd1, 'd2> { x } fn d1<'a, 'b, 'c, 'd1, 'd2>( x: Multi<'a, 'b, 'c, 'static, 'd2> ) -> Multi<'a, 'b, 'c, 'd1, 'd2> { x }
'bis invariant, because it is "inside" the mutableCellcontext.
Exercise: try writing a function that fails to compile because
'bis invariant.
'cis contravariant, because it shows up in the parameter to a callback.
#![allow(dead_code)] fn main() {} use std::cell::Cell; struct Multi<'a, 'b, 'c, 'd1, 'd2> { a: &'a str, b: Cell<&'b str>, c: fn(&'c str) -> usize, d: &'d1 mut &'d2 str, } fn a<'a, 'b, 'c, 'd1, 'd2>( x: Multi<'static, 'b, 'c, 'd1, 'd2> ) -> Multi<'a, 'b, 'c, 'd1, 'd2> { x } fn c<'a, 'b, 'c, 'd1, 'd2>( x: Multi<'a, 'b, 'c, 'd1, 'd2> ) -> Multi<'a, 'b, 'static, 'd1, 'd2> { x } fn d1<'a, 'b, 'c, 'd1, 'd2>( x: Multi<'a, 'b, 'c, 'static, 'd2> ) -> Multi<'a, 'b, 'c, 'd1, 'd2> { x }
'd1is covariant! Even though it is a mutable reference, it is not "inside" the&mutpointer.
#![allow(dead_code)] fn main() {} use std::cell::Cell; struct Multi<'a, 'b, 'c, 'd1, 'd2> { a: &'a str, b: Cell<&'b str>, c: fn(&'c str) -> usize, d: &'d1 mut &'d2 str, } fn a<'a, 'b, 'c, 'd1, 'd2>( x: Multi<'static, 'b, 'c, 'd1, 'd2> ) -> Multi<'a, 'b, 'c, 'd1, 'd2> { x } fn c<'a, 'b, 'c, 'd1, 'd2>( x: Multi<'a, 'b, 'c, 'd1, 'd2> ) -> Multi<'a, 'b, 'static, 'd1, 'd2> { x } fn d1<'a, 'b, 'c, 'd1, 'd2>( x: Multi<'a, 'b, 'c, 'static, 'd2> ) -> Multi<'a, 'b, 'c, 'd1, 'd2> { x }
'd2is invariant, because it is "inside" a&mutreference.