我可以强制性状是协变的吗?

时间:2018-12-28 14:26:50

标签: rust traits covariance

由于@francis-gagné的excellent answer有另一个问题,我对方差的工作原理有一个更清晰的认识。例如,包含引用的类型在其生命周期参数上是协变的,如下所示。

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
}

另一方面,接受引用(或包含引用的类型)的函数与其生命周期参数相反。

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
}

最后,具有生存期参数的 trait 在其生存期参数上是不变的。

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
}

这是有道理的,因为可以同时使用协变和反变类型来实现特征,如下所示。

impl<'a> Baz<'a> for Foo<'a> {}
impl<'a> Baz<'a> for Bar<'a> {}

我的问题是:我可以强制某个特征在其生存期参数上进行协变吗?我希望有一个标记特征,例如:

trait Baz<'a>: Covariant<'a> {}

这将使其以反变类型实现该特征是非法的,并允许za成为上述v函数中向量test_baz的成员。

当然,能够做相反的事情(迫使一个性状成为逆变)也可能是有用的...

Examples in the playground

2 个答案:

答案 0 :(得分:2)

否。

您可以表达“为 any Baz<'x>实现'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];
}

但是您(从Rust 1.31开始)不能编写Box<dyn for<'x: 'b> Baz<'x>>,即使可以,该语法也只能使用一生。它不会让您表达类型参数的协方差。

答案 1 :(得分:2)

我找到了解决方法。我没有将特征标记为协变变量(如@trentcl指出,在Rust 1.31中是不可能的),而是使该类型在所有生命周期中实现特征均小于其自身寿命:

impl<'a:'b, 'b> Baz<'b> for Foo<'a> {}

这样,只要需要Foo<'b>,我就可以使用Foo<'a> Bar<'b>的实例:

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
}

当然,这要求特征的每个实现者都遵循这种模式,因此它不像将特征 itself 标记为协变一样强大。但这在某些情况下可以解决问题。

Example in the playground