如何处理 Rust 中的包装类型不变性?

2024-02-24

对包装类型的引用,例如&Rc<T> and &Box<T>是不变的T (&Rc<T>不是一个&Rc<U>即使T is a U)。该问题的具体示例():

use std::rc::Rc;
use std::rc::Weak;

trait MyTrait {}

struct MyStruct {
}

impl MyTrait for MyStruct {}

fn foo(rc_trait: Weak<MyTrait>) {}

fn main() {
    let a = Rc::new(MyStruct {});
    foo(Rc::downgrade(&a));
}

此代码会导致以下错误:

<anon>:15:23: 15:25 error: mismatched types:
 expected `&alloc::rc::Rc<MyTrait>`,
    found `&alloc::rc::Rc<MyStruct>`

类似的例子(有类似的错误)Box<T> ():

trait MyTrait {}

struct MyStruct {
}

impl MyTrait for MyStruct {}

fn foo(rc_trait: &Box<MyTrait>) {}

fn main() {
    let a = Box::new(MyStruct {});
    foo(&a);
}

在这些情况下我当然可以只注释a具有所需的类型,但在许多情况下这是不可能的,因为还需要原始类型。那我该怎么办呢?


您在这里看到的内容与方差和子类型根本无关。

首先,有关 Rust 子类型的信息最丰富的读物是本章 https://doc.rust-lang.org/nightly/nomicon/subtyping.html诺米康。您可以在 Rust 子类型关系中找到这一点(即,当您可以将一种类型的值传递给需要以下变量的函数或变量时)不同的类型)非常有限。只有当你使用生命周期时才能观察到它。

例如,下面的代码显示了如何准确地&Box<T>是(共同)变体:

fn test<'a>(x: &'a Box<&'a i32>) {}

fn main() {
    static X: i32 = 12;
    let xr: &'static i32 = &X;
    let xb: Box<&'static i32> = Box::new(xr);  // <---- start of box lifetime
    let xbr: &Box<&'static i32> = &xb;
    test(xbr);  // Covariance in action: since 'static is longer than or the 
                // same as any 'a, &Box<&'static i32> can be passed to
                // a function which expects &'a Box<&'a i32>
                //
                // Note that it is important that both "inner" and "outer"
                // references in the function signature are defined with
                // the same lifetime parameter, and thus in `test(xbr)` call
                // 'a gets instantiated with the lifetime associated with
                // the scope I've marked with <----, but nevertheless we are
                // able to pass &'static i32 as &'a i32 because the
                // aforementioned scope is less than 'static, therefore any
                // shared reference type with 'static lifetime is a subtype of
                // a reference type with the lifetime of that scope
}  // <---- end of box lifetime

该程序可以编译,这意味着两者& and Box在它们各自的类型和生命周期参数上是协变的。

与大多数具有 C++ 和 Java 等类/接口的“传统”OOP 语言不同,Rust 特征不引入子类型关系。尽管说,

trait Show {
    fn show(&self) -> String;
}

高度相似

interface Show {
    String show();
}

在像Java这样的某些语言中,它们在语义上有很大不同。在 Rust 中,当用作类型时,裸特征是never实现此特征的任何类型的超类型:

impl Show for i32 { ... }

// the above does not mean that i32 <: Show

Show,虽然是一种特质,但确实can用于类型位置,但它表示一个特殊的无尺寸类型只能用于形成特质对象 http://doc.rust-lang.org/book/trait-objects.html。您不能拥有裸特征类型的值,因此谈论裸特征类型的子类型和方差甚至没有意义。

特征对象的形式为&SomeTrait or &mut SomeTrait or SmartPointer<SomeTrait>, 和他们can被传递并存储在变量中,并且需要它们来抽象特征的实际实现。然而,&T where T: SomeTrait is not的一个子类型&SomeTrait,并且这些类型根本不参与方差。

Trait 对象和常规指针具有不兼容的内部结构:&T只是一个指向具体类型的常规指针T, while &SomeTrait是一个胖指针,其中包含指向实现的类型的原始值的指针SomeTrait还有第二个指向 vtable 的指针,用于实现SomeTrait上述类型的。

事实是经过&T as &SomeTrait or Rc<T> as Rc<SomeTrait>工作之所以发生,是因为 Rust 确实如此自动强制对于引用和智能指针:它能够构造一个胖指针&SomeTrait供定期参考&T如果它知道T;我相信这是很自然的。例如,你的例子Rc::downgrade()有效是因为Rc::downgrade()返回类型的值Weak<MyStruct>这被强迫Weak<MyTrait>.

然而,构建&Box<SomeTrait> out of &Box<T> if T: SomeTrait更为复杂:首先,编译器需要分配一个new临时值,因为Box<T> and Box<SomeTrait>有不同的记忆表示。如果你有,请说,Box<Box<T>>,得到Box<Box<SomeTrait>>它甚至更复杂,因为它需要在堆上创建新的分配储藏Box<SomeTrait>。因此,嵌套引用和智能指针没有自动强制,而且这与子类型和方差根本没有关系。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何处理 Rust 中的包装类型不变性? 的相关文章

随机推荐