rust - 我如何处理 Rust 中的包装器类型不变性?

标签 rust wrapper invariance

对包装器类型的引用,如 &Rc<T>&Box<T>T 中不变( &Rc<T> 不是 &Rc<U> 即使 TU )。该问题的具体示例 ( 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 ):

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 子类型化的信息最丰富的读物是 this chapter Nomicon 的。你会发现在 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

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

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

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

非常相似

interface Show {
    String show();
}

在某些语言中,如 Java,它们在语义上有很大不同。在 Rust 裸特征中,当用作类型时,从不是任何实现此特征的类型的父类(super class)型:

impl Show for i32 { ... }

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

Show ,虽然是一个特征,但确实可以用于类型位置,但它表示一个特殊的未调整大小的类型,它只能用于形成trait objects .您不能拥有裸特征类型的值,因此谈论带有裸特征类型的子类型和变体甚至没有意义。

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

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

传递 &T 的事实作为&SomeTraitRc<T>作为Rc<SomeTrait>工作的发生是因为 Rust 对引用和智能指针自动强制转换:它能够构造一个胖指针 &SomeTrait定期引用&T如果它知道T ;我相信这是很自然的。例如,您的示例 Rc::downgrade()工作是因为 Rc::downgrade()返回 Weak<MyStruct> 类型的值被强制为 Weak<MyTrait> .

但是,构建 &Box<SomeTrait>来自 &Box<T>如果T: SomeTrait复杂得多:一方面,编译器需要分配一个临时值,因为Box<T>Box<SomeTrait>有不同的内存表示。如果你有,比方说,Box<Box<T>> , 得到 Box<Box<SomeTrait>>离开它甚至更复杂,因为它需要在堆上创建一个新的分配来存储 Box<SomeTrait> .因此,嵌套引用和智能指针没有自动强制转换,而且,这与子类型化和变体完全无关。

关于rust - 我如何处理 Rust 中的包装器类型不变性?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37580570/

相关文章:

python - 向现有类添加功能的最简单方法

c++ - 如何制作 C++ native 项目的 CLI/C++ 包装器

ocaml - OCaml方差(+'a,-'a)和不变性

rust - Rust 的 clippy 可以自动更正/自动修复吗?

rust - 有没有办法将类型名称放入 Rust 宏中?

c++ - section.data 中错误的 reloc 地址 0x0 python 的 C 扩展

generics - Kotlin 中的方差/协方差泛型

scala - 为什么所有不变的泛型类位置在 Scala 的类型参数列表中都是不变的?

rust - 为什么在 BTreeSet 和 HashSet 之间切换时,Bron-Kerbosch 算法会得到不同的结果?

rust - 是否可以在不分配新 Vec 的情况下将函数映射到 Vec 上?