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

&Rc<T>中,对&Box<T>T等包装类型的引用是不变的(即使&Rc<T>&Rc<U>也不是T)。这个问题的一个具体例子(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>`

具有URust Playground)的类似示例(具有类似错误):
trait MyTrait {}

struct MyStruct {
}

impl MyTrait for MyStruct {}

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

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

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

最佳答案

你在这里看到的与变异和亚类型完全无关。
首先,关于锈菌亚型的阅读资料最多的是Nomicon的this chapter。您可以发现,在Rust子类型关系中(即,当您可以将一种类型的值传递给一个函数或一个需要不同类型变量的变量时),这种关系非常有限。它只能在你有生之年工作时才能被观察到。
例如,下面的代码显示了&Box<T>是(co)变量的确切程度:

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在各自的类型和生存期参数上都是协变的。
与大多数传统的OOP语言不同,它具有C++和Java之类的类/接口,在生锈特性中不引入子类型关系。尽管,比如说,
trait Show {
    fn show(&self) -> String;
}

高度相似
interface Show {
    String show();
}

在Java这样的语言中,它们在语义上有很大的不同。在铁锈中,裸性状作为一种类型使用时,决不是实现这种性状的任何类型的超型:
impl Show for i32 { ... }

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

Show虽然是一种特征,但确实可以用于类型位置,但它表示一种特殊的非大小化类型,只能用于形成trait objects。您不能拥有裸特征类型的值,因此,与裸特征类型讨论子类型和方差甚至是没有意义的。
Trait对象以&SomeTrait&mut SomeTraitSmartPointer<SomeTrait>的形式存在,它们可以被传递并存储在变量中,需要它们来抽象出Trait的实际实现。然而,&T其中T: SomeTrait不是&SomeTrait的子类型,并且这些类型根本不参与方差。
Trait对象和正则指针具有不兼容的内部结构:&T只是一个指向具体类型T的正则指针,&SomeTrait是一个胖指针,它包含一个指向实现SomeTrait的类型的原始值的指针,以及一个指向用于实现上述类型的SomeTrait的vtable的第二个指针。
&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>>。因此,对嵌套引用和智能指针没有自动强制,而且,这与子类型和方差根本没有关系。

本文翻译自 https://stackoverflow.com/questions/37580570/

网站遵循 CC BY-SA 4.0 协议,转载或引用请注明出处。

标签 rust wrapper invariance


相关文章:

rust - 此“原子” Rust代码与其“非原子”对应物之间有什么区别?

rust - 为什么借来的字符串文字可以伪造一辈子而超过其所有者?

rust - 迭代器,懒惰和所有权

c# - 如何使用包装在C ++中的COM接口部署C#库?

c++ - 抛出函数式可变参数宏包装,替换抛出的异常

java - 在Stream.reduce()这样的API中选择不变性有什么好理由?

rust - Rust中的动态调度

c# - ObjectSet包装器无法与linqToEntities子查询一起使用

java - 为什么可以强制转换泛型类?

c# - 我已经阅读了所有有关协方差,协方差和不变性的文章,但是我仍然不知道如何设计代码