我可以强制一个特征是协变的吗?

Pie*_*ine 6 covariance traits rust

感谢 @francis-gagn\xc3\xa9 \对另一个问题的出色回答,我对方差的工作原理有了更清晰的了解。例如,包含引用的类型在其生命周期参数上是协变的,如下所示。

\n\n
struct Foo<\'a> (PhantomData<&\'a str>);\n\n/// Foo is covariant over its lifetime parameter\npub fn test_foo<\'a:\'b, \'b:\'c, \'c>() {\n    let fa: Foo<\'a> = Foo(PhantomData);\n    let fb: Foo<\'b> = Foo(PhantomData);\n    let fc: Foo<\'c> = Foo(PhantomData);\n\n    let v: Vec<Foo<\'b>> = vec![fa, fb]; // fc is not accepted\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

另一方面,接受引用(或包含引用的类型)的函数在其生命周期参数上是逆变的。

\n\n
struct Bar<\'a> (PhantomData<fn(&\'a str)>);\n\n/// Bar is contravariant over its lifetime parameter\npub fn test_bar<\'a:\'b, \'b:\'c, \'c>() {\n    let ba: Bar<\'a> = Bar(PhantomData);\n    let bb: Bar<\'b> = Bar(PhantomData);\n    let bc: Bar<\'c> = Bar(PhantomData);\n\n    let v: Vec<Bar<\'b>> = vec![bb, bc]; // ba is not accepted\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

最后,具有生命周期参数的特征在其生命周期参数内是不变的。

\n\n
pub trait Baz<\'a> {}\n\nimpl<\'a> Baz<\'a> for () {}\n\n/// Baz is invariant over its lifetime parameter\npub fn test_baz<\'a:\'b, \'b:\'c, \'c>() {\n    let za: Box<dyn Baz<\'a>> = Box::new(());\n    let zb: Box<dyn Baz<\'b>> = Box::new(());\n    let zc: Box<dyn Baz<\'c>> = Box::new(());\n\n    let v: Vec<Box<dyn Baz<\'b>>> = vec![zb]; // za and zx are not accepted\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

这是有道理的,因为该特征可以通过协变和逆变类型来实现,如下所示。

\n\n
impl<\'a> Baz<\'a> for Foo<\'a> {}\nimpl<\'a> Baz<\'a> for Bar<\'a> {}\n
Run Code Online (Sandbox Code Playgroud)\n\n

我的问题是:我可以强制一个特征在其生命周期参数上保持协变吗?我期望有一个标记特征,例如:

\n\n
trait Baz<\'a>: Covariant<\'a> {}\n
Run Code Online (Sandbox Code Playgroud)\n\n

这将使使用逆变类型实现该特征变得非法,并允许za成为上述函数v中向量的成员test_baz

\n\n

当然,能够做相反的事情(强制一个特征是逆变的)也可能很有用......

\n\n

操场上的例子

\n

Pie*_*ine 5

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

impl<'a:'b, 'b> Baz<'b> for Foo<'a> {}
Run Code Online (Sandbox Code Playgroud)

这样,只要需要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
}
Run Code Online (Sandbox Code Playgroud)

当然,这要求该特征的每个实现者都遵循这种模式,因此它不如将特征本身标记为协变那么强大。但在某些情况下它可以发挥作用。

操场上的例子


tre*_*tcl 3

不。

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];
}
Run Code Online (Sandbox Code Playgroud)

但是你不能(从 Rust 1.31 开始)编写Box<dyn for<'x: 'b> Baz<'x>>,即使你可以,该语法也只能在生命周期内有效;它不允许您表达类型参数的协方差。