Can I force a trait to be covariant?

女生的网名这么多〃 提交于 2021-01-27 16:51:27

问题


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


回答1:


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.




回答2:


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



来源:https://stackoverflow.com/questions/53960041/can-i-force-a-trait-to-be-covariant

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!