7

对包装器类型的引用&Rc<T>和在( is not a even if is a )&Box<T>中是不变的。这个问题的一个具体例子(Rust Playground):T&Rc<T>&Rc<U>TU

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,但在许多情况下这是不可能的,因为还需要原始类型。那我该怎么办?

4

2 回答 2

5

您在此处看到的内容与方差和子类型完全无关。

首先,关于 Rust 子类型的信息最丰富的读物是 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它们各自的类型和生命周期参数都是协变的。

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

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

高度相似

interface Show {
    String show();
}

在像 Java 这样的语言中,它们在语义上是完全不同的。在 Rust 裸 trait 中,当用作类型时,绝不是实现此 trait 的任何类型的超类型:

impl Show for i32 { ... }

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

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

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

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

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

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

于 2016-06-02T12:52:54.770 回答
3

在这种情况下,Rc::downgrade这实际上只是在这种特殊情况下类型推断的失败,如果它作为单独的 let 完成,它将起作用:

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

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

操场

因为Box<T>很可能您实际上并不想要对框的引用作为参数,而是对内容的引用。在这种情况下,没有不变性要处理:

fn foo(rc_trait: &MyTrait) {}

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

操场

类似地,对于 的情况Rc<T>,如果您编写一个带有 an 的函数,Rc<T>您可能想要一个克隆(即引用计数的引用),而不是普通的引用:

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

fn main() {
    let a = Rc::new(MyStruct {});
    foo(a.clone());
}

操场

于 2016-06-01T23:50:10.493 回答