我很难理解代数数据类型中关于特征的规则.

use std::rc::Rc;
use std::cell::RefCell;

trait Quack {
    fn quack(&self);
}

struct Duck;

impl Quack for Duck {
    fn quack(&self) { println!("Quack!"); }
}

fn main() {
    let mut pond: Vec<Box<Quack>> = Vec::new();
    let duck: Box<Duck> = Box::new(Duck);
    pond.push(duck); // This is valid.

    let mut lake: Vec<Rc<RefCell<Box<Quack>>>> = Vec::new();
    let mallard: Rc<RefCell<Box<Duck>>> = Rc::new(RefCell::new(Box::new(Duck)));
    lake.push(mallard); // This is a type mismatch.
}

上述代码无法编译,产生以下错误消息:

 expected `alloc::rc::Rc<core::cell::RefCell<Box<Quack>>>`,
    found `alloc::rc::Rc<core::cell::RefCell<Box<Duck>>>`
(expected trait Quack,
    found struct `Duck`) [E0308]
src/main.rs:19     lake.push(mallard);

为什么pond.push(duck)有效,而lake.push(mallard)无效?在这两种情况下,在预期为Quack的位置提供了Duck.在前者中,编译器很高兴,但在后者中,编译器却不高兴.

这种差异的原因与CoerceUnsized有关吗?

推荐答案

这是一种正确的行为,即使有点不幸.

在第一种情况下,我们有:

let mut pond: Vec<Box<Quack>> = Vec::new();
let duck: Box<Duck> = Box::new(Duck);
pond.push(duck);

请注意,当在Vec<Box<Quack>>上调用push()时,接受Box<Quack>,您将通过Box<Duck>.这没关系——rustc能够理解您想要将装箱的值转换为trait对象,如下所示:

let duck: Box<Duck> = Box::new(Duck);
let quack: Box<Quack> = duck;  // automatic coercion to a trait object

在第二种情况下,我们有:

let mut lake: Vec<Rc<RefCell<Box<Quack>>>> = Vec::new();
let mallard: Rc<RefCell<Box<Duck>>> = Rc::new(RefCell::new(Box::new(Duck)));
lake.push(mallard);

这里push()接受Rc<RefCell<Box<Quack>>>,而您提供Rc<RefCell<Box<Duck>>>:

let mallard: Rc<RefCell<Box<Duck>>> = Rc::new(RefCell::new(Box::new(Duck)));
let quack: Rc<RefCell<Box<Quack>>> = mallard;

现在有麻烦了.Box<T>是与DST兼容的类型,因此它可以用作trait对象的容器.当this RFC实现后,Rc和其他智能指针也很快会出现同样的情况.然而,在这种情况下,不存在从具体类型到特征对象的强制,因为Box<Duck>位于其他类型层(Rc<RefCell<..>>)的内部.

记住,trait对象是一个胖指针,所以Box<Duck>Box<Quack>的大小不同.因此,原则上,它们并不直接兼容:不能只取Box<Duck>字节,然后将它们写入预期为Box<Quack>的位置.Rust执行一种特殊的转换,即获取一个指向Duck的虚拟表的指针,构造一个fat指针并将其写入Box<Quack>类型的变量.

然而,当您有Rc<RefCell<Box<Duck>>>个时,rustc需要知道如何构造和分解RefCellRc,以便将相同的胖指针转换应用到其内部.当然,因为这些都是库类型,所以它不知道怎么做.这也适用于任何其他包装类型,例如ArcMutex甚至Vec.你没想到会用Vec<Box<Duck>>代替Vec<Box<Quack>>,对吧?

还有一个事实是,在Rc的例子中,Box<Duck>Box<Quack>中创建的Rcs不会被连接——它们会有不同的参考计数器.

也就是说,只有当您可以直接访问支持DST的智能指针时,才能从具体类型转换为特征对象,而不是当它隐藏在其他 struct 中时.

这就是说,我看到了在一些特定的类型中允许这样做的可能性.例如,我们可以引入一些编译器已知的Construct/Unwrap特征,它可以使用这些特征"到达"包装器堆栈内部,并在其中执行特征对象转换.然而,还没有人设计这个东西并提供RFC——可能是因为它不是一个广泛需要的功能.

Rust相关问答推荐

从特征实现调用函数的Rust惯用方法

Rust:跨多个线程使用hashmap Arc和rwlock

什么时候铁 rust FFI边界上的panic 是未定义的行为?

S,一般性状和联想型性状有什么不同?

什么时候和为什么S最好是按值或引用传递简单类型

我如何制作一个变异迭代器来锁定内部数据直到删除?

rust 蚀生命周期 不匹配-不一定超过此处定义的生命周期

对reqwest提供的这种嵌套JSON struct 进行反序列化

找不到 .has_func 或 .get_func 的 def

为什么将易错函数的泛型结果作为泛型参数传递 infer ()?不应该是暧昧的吗?

如何将一个矩阵的列分配给另一个矩阵,纳尔代数?

当没有实际结果时,如何在 Rust 中强制执行错误处理?

面临意外的未对齐指针取消引用:地址必须是 0x8 的倍数,但为 0x__错误

Rust中是否可以在不复制的情况下从另一个不可变向量创建不可变向量?

&str 的编译时拆分是否可能?

为什么指定生命周期让我返回一个引用?

为什么 no_std crate 可以依赖于使用 std 的 crate?

传递 Option<&mut T> 时何时需要 mut

如何在 Rust 中构建一个 str

在同一向量 Rust 中用另一个字符串扩展一个字符串