Thanks to @francis-gagné 's excellent answer to another question, I have a clearer view of how variance works. For example, a type containing a reference is covariant over its lifetime parameter, as demonstrated below.
struct Foo<'a> (PhantomData<&'a str>);
/// Foo is covariant over its lifetime parameter
pub fn test_foo<'a:'b, 'b:'c, 'c>() {
let fa: Foo<'a> = Foo(PhantomData);
let fb: Foo<'b> = Foo(PhantomData);
let fc: Foo<'c> = Foo(PhantomData);
let v: Vec<Foo<'b>> = vec![fa, fb]; // fc is not accepted
}
On the other hand a function accepting a reference (or a type containing it) is contravariant over its lifetime parameter.
struct Bar<'a> (PhantomData<fn(&'a str)>);
/// Bar is contravariant over its lifetime parameter
pub fn test_bar<'a:'b, 'b:'c, 'c>() {
let ba: Bar<'a> = Bar(PhantomData);
let bb: Bar<'b> = Bar(PhantomData);
let bc: Bar<'c> = Bar(PhantomData);
let v: Vec<Bar<'b>> = vec![bb, bc]; // ba is not accepted
}
Finally, a trait with a lifetime parameter is invariant over its lifetime parameter.
pub trait Baz<'a> {}
impl<'a> Baz<'a> for () {}
/// Baz is invariant over its lifetime parameter
pub fn test_baz<'a:'b, 'b:'c, 'c>() {
let za: Box<dyn Baz<'a>> = Box::new(());
let zb: Box<dyn Baz<'b>> = Box::new(());
let zc: Box<dyn Baz<'c>> = Box::new(());
let v: Vec<Box<dyn Baz<'b>>> = vec![zb]; // za and zx are not accepted
}
That makes sense, because the trait could be implemented both by a covariant and a contravariant type, as illustrated below.
impl<'a> Baz<'a> for Foo<'a> {}
impl<'a> Baz<'a> for Bar<'a> {}
My question is: can I force a trait to be covariant over its lifetime parameter? I would expect a marker trait such as:
trait Baz<'a>: Covariant<'a> {}
that would make it illegal to implement that trait with a contravariant type, and allow za
to be a member of the vector v
in the test_baz
function above.
Of course, being able to do the opposite (force a trait to be contravariant) could be useful as well...
Examples in the playground
I found a workaround. Rather than marking the trait as covariant (which, as @trentcl noted, is not possible in Rust 1.31), I made the type implement the trait for all lifetimes smaller than its own:
impl<'a:'b, 'b> Baz<'b> for Foo<'a> {}
That way, I can use instances of Foo<'b>
and Foo<'a>
whenever a Bar<'b>
is required:
pub fn test_baz<'a:'b, 'b:'c, 'c>() {
let fa: Foo<'a> = Foo(PhantomData);
let fb: Foo<'b> = Foo(PhantomData);
let fc: Foo<'c> = Foo(PhantomData);
let v: Vec<&dyn Baz<'b>> = vec![&fa, &fb]; // &fc is not accepted
}
Of course, that requires every implementer of the trait to follow this pattern, so it is not as powerful as marking the trait itself as covariant. But it can do the trick in some situations.
Example in the playground
No.
You can express "a value that implements Baz<'x>
for any 'x
":
pub fn test_baz<'a:'b, 'b:'c, 'c>() {
let za: Box<dyn for<'x> Baz<'x>> = Box::new(());
let zb: Box<dyn for<'x> Baz<'x>> = Box::new(());
let zc: Box<dyn for<'x> Baz<'x>> = Box::new(());
let v: Vec<Box<dyn for<'x> Baz<'x>>> = vec![za, zb, zc];
}
But you can't (as of Rust 1.31) write Box<dyn for<'x: 'b> Baz<'x>>
, and even if you could, that syntax would only work for lifetimes; it wouldn't let you express covariance over type parameters.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With