您在此处看到的内容与方差和子类型完全无关。
首先,关于 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 类型的变异甚至没有意义。
特征对象采用&SomeTrait
or &mut SomeTrait
or的形式SmartPointer<SomeTrait>
,它们可以被传递并存储在变量中,并且需要它们来抽象出特征的实际实现。但是,&T
whereT: SomeTrait
不是的子类型&SomeTrait
,并且这些类型根本不参与方差。
特征对象和常规指针具有不兼容的内部结构:&T
只是一个指向具体类型的常规指针T
,&SomeTrait
而是一个胖指针,它包含一个指向实现的类型的原始值的指针,SomeTrait
以及一个指向用于实现的 vtable 的第二个指针SomeTrait
属于上述类型。
&T
传递as&SomeTrait
或Rc<T>
as起作用的事实是Rc<SomeTrait>
因为 Rust对引用和智能指针进行自动强制:如果它知道,它能够&SomeTrait
为常规引用构造一个胖指针;这是很自然的,我相信。例如,您的示例之所以有效,是因为返回了一个类型的值,该值被强制转换为.&T
T
Rc::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>
。因此,嵌套引用和智能指针没有自动强制转换,而且这与子类型和方差完全无关。