我能强制一个特质具有协变性吗?

6

感谢@francis-gagné对另一个问题的精彩回答,我更加清晰地了解了变异如何工作。例如,包含引用的类型在其生命周期参数上是协变的,如下所示。

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成为上面的test_baz函数中v向量的一员。

当然,能够执行相反的操作(强制使特性逆变)也可能很有用...

在playground中查看示例


1
这里是一篇总结Rust中所有方差规则的文章, 也许有人会觉得有用。 - CodeSandwich
2个回答

5
我找到了一个变通方法。不要将特质标记为协变(正如@trentcl所指出的那样,在Rust 1.31中不可能实现),而是让该类型实现小于其自身生命周期的所有生命周期的特质:
impl<'a:'b, 'b> Baz<'b> for Foo<'a> {}

那样,当需要一个 Bar<'b> 时,我可以使用 Foo<'b>Foo<'a> 的实例:
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
}

当然,这需要每个实现trait的人都遵循这种模式,因此它不像将trait本身标记为协变那样强大。但在某些情况下它可以解决问题。 在playground中的示例

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];
}

但是你不能(在Rust 1.31中)编写Box<dyn for<'x: 'b> Baz<'x>>,即使你可以,这种语法也只适用于生命周期;它不能让你表达类型参数上的协变性。


1
我想添加更多的解释或提出一些建设性的建议,但很难确定问题的确切意思,所以这是简单的答案。如果您有需要此类功能的具体代码示例,我很乐意查看,并可能提出建议。 - trent
我的问题非常复杂,很难概括,而且我甚至不确定协变特质是否是我需要的解决方案... 我主要是想更好地理解整个情况。 - Pierre-Antoine

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接