【问题标题】:Can I force a trait to be covariant?我可以强制一个特征是协变的吗?
【发布时间】:2019-01-02 16:26:05
【问题描述】:

感谢@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 成为上述test_baz 函数中向量v 的成员。

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

Examples in the playground

【问题讨论】:

标签: rust traits covariance


【解决方案1】:

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

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

这样,我可以在需要Bar&lt;'b&gt; 时使用Foo&lt;'b&gt; Foo&lt;'a&gt; 的实例:

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 本身标记为协变那么强大。但在某些情况下它可以解决问题。

Example in the playground

【讨论】:

    【解决方案2】:

    没有。

    您可以表示“为 any 'x 实现 Baz&lt;'x&gt; 的值”:

    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&lt;dyn for&lt;'x: 'b&gt; Baz&lt;'x&gt;&gt;,即使你可以,这种语法也只能在生命周期内有效;它不会让你表达类型参数的协方差。

    【讨论】:

    • 我想补充更多细节,或者提出一个有用的建议,但很难说出这个问题到底是为了什么,所以这是简单的答案。如果你有具体的代码需要这样的功能,我很想看一个示例,也许我可以提出建议。
    • 我的问题很复杂,很难总结,我什至不确定协变特征是否是我需要的解决方案......我主要是想更好地理解如果整个画面。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-10-05
    • 1970-01-01
    • 2011-05-13
    • 2010-11-18
    • 2013-03-16
    相关资源
    最近更新 更多