如何处理 Rust 中的包装器类型不变性? [英] How do I deal with wrapper type invariance in Rust?

查看:74
本文介绍了如何处理 Rust 中的包装器类型不变性?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

&Rc&Box 等包装器类型的引用在 T (&Rc 不是 &Rc,即使 TU).该问题的具体示例(铁锈游乐场):

References to wrapper types like &Rc<T> and &Box<T> are invariant in T (&Rc<T> is not a &Rc<U> even if T is a U). A concrete example of the issue (Rust Playground):

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> 的类似示例(具有类似错误)(Rust Playground):

Similar example (with similar error) with Box<T> (Rust Playground):

trait MyTrait {}

struct MyStruct {
}

impl MyTrait for MyStruct {}

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

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

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

In these cases I could of course just annotate a with the desired type, but in many cases that won't be possible because the original type is needed as well. So what do I do then?

推荐答案

您在此处看到的与差异和子类型完全无关.

What you see here is not related to variance and subtyping at all.

首先,关于 Rust 子类型的最有用的读物​​是本章 的 Nomicon.您会发现在 Rust 子类型关系中(即,当您可以将一种类型的值传递给需要 不同 类型变量的函数或变量时)是非常有限的.只有在使用生命周期时才能观察到.

First, the most informative read on subtyping in Rust is this chapter of Nomicon. You can find there that in Rust subtyping relationship (i.e. when you can pass a value of one type to a function or a variable which expects a variable of different type) is very limited. It can only be observed when you're working with lifetimes.

例如,下面的一段代码显示了 &Box 是 (co)variant 的准确程度:

For example, the following piece of code shows how exactly &Box<T> is (co)variant:

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

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

This program compiles, which means that both & and Box are covariant over their respective type and lifetime parameters.

与大多数具有 C++ 和 Java 等类/接口的传统"OOP 语言不同,在 Rust 特征中不引入子类型关系.即使,比如说,

Unlike most of "conventional" OOP languages which have classes/interfaces like C++ and Java, in Rust traits do not introduce subtyping relationship. Even though, say,

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

非常相似

interface Show {
    String show();
}

在某些语言(如 Java)中,它们的语义完全不同.在 Rust 裸 trait 中,当用作类型时,永远是实现此 trait 的任何类型的超类型:

in some language like Java, they are quite different in semantics. In Rust bare trait, when used as a type, is never a supertype of any type which implements this trait:

impl Show for i32 { ... }

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

Show,虽然是一个trait,确实可以用在类型位置,但是它表示一个特殊的unsized类型,它只能是用于形成 trait 对象.你不能拥有裸特征类型的值,因此谈论裸特征类型的子类型和变化甚至没有意义.

Show, while being a trait, indeed can be used in type position, but it denotes a special unsized type which can only be used to form trait objects. You cannot have values of the bare trait type, therefore it does not even make sense to talk about subtyping and variance with bare trait types.

Trait 对象采用 &SomeTrait&mut SomeTraitSmartPointer 的形式,并且它们可以 被传递并存储在变量中,并且需要它们来抽象出特征的实际实现.但是,&T 其中 T: SomeTrait 不是 &SomeTrait 的子类型,而这些类型确实根本不参与方差.

Trait objects take form of &SomeTrait or &mut SomeTrait or SmartPointer<SomeTrait>, and they can be passed around and stored in variables and they are needed to abstract away the actual implementation of the trait. However, &T where T: SomeTrait is not a subtype of &SomeTrait, and these types do not participate in variance at all.

Trait 对象和常规指针具有不兼容的内部结构:&T 只是指向具体类型 T 的常规指针,而 &SomeTrait 是一个胖指针,它包含一个指向实现 SomeTrait 的类型的原始值的指针,以及一个指向上述 SomeTrait 实现的虚表的第二个指针类型.

Trait objects and regular pointers have incompatible internal structure: &T is just a regular pointer to a concrete type T, while &SomeTrait is a fat pointer which contains a pointer to the original value of a type which implements SomeTrait and also a second pointer to a vtable for the implementation of SomeTrait of the aforementioned type.

&T 作为 &SomeTraitRc 作为 Rc 传递的事实code> 之所以起作用,是因为 Rust 对引用和智能指针进行自动强制转换:它能够为常规引用构造一个胖指针 &SomeTrait&T 如果它知道T;我相信这是很自然的.例如,您使用 Rc::downgrade() 的示例有效,因为 Rc::downgrade() 返回 Weak 类型的值它被强制为 Weak.

The fact that passing &T as &SomeTrait or Rc<T> as Rc<SomeTrait> works happens because Rust does automatic coercion for references and smart pointers: it is able to construct a fat pointer &SomeTrait for a regular reference &T if it knows T; this is quite natural, I believe. For instance, your example with Rc::downgrade() works because Rc::downgrade() returns a value of type Weak<MyStruct> which gets coerced to Weak<MyTrait>.

然而,如果 T: SomeTrait 构建 &Box&Box 则要复杂得多:一方面,编译器需要分配一个 new 临时值,因为 BoxBox 具有不同的内存表示.如果你有,比如说,Box>,从它中取出 Box> 就更复杂了,因为它需要在堆上创建一个新的分配来存储Box.因此,没有对嵌套引用和智能指针的自动强制,同样,这与子类型和变异完全没有关系.

However, constructing &Box<SomeTrait> out of &Box<T> if T: SomeTrait is much more complex: for one, the compiler would need to allocate a new temporary value because Box<T> and Box<SomeTrait> has different memory representations. If you have, say, Box<Box<T>>, getting Box<Box<SomeTrait>> out of it is even more complex, because it would need creating a new allocation on the heap to store Box<SomeTrait>. Thus, there are no automatic coercions for nested references and smart pointers, and again, this is not connected with subtyping and variance at all.

这篇关于如何处理 Rust 中的包装器类型不变性?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆