代数数据类型的特征

时间:2015-06-05 02:31:02

标签: rust

我无法理解代数数据类型中的特征规则。 这是一个简化的例子:

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)不是?在这两种情况下,都提供Duck,其中Quack是预期的。在前者中,编译器很高兴,但在后者中,它不是。

这种差异的原因是否与CoerceUnsized相关?

2 个答案:

答案 0 :(得分:6)

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

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

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

请注意,push()Vec<Box<Quack>>上调用时,会接受Box<Quack>,并且您正在通过Box<Duck>。这没关系 - rustc能够理解您想要将盒装值转换为特征对象,如下所示:

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兼容的类型,因此它可以用作特征对象的容器。 Rc和其他智能指针在实现this RFC时很快就会出现同样的情况。但是,在这种情况下,没有从具体类型到特征对象的强制,因为Box<Duck>位于其他类型的层(Rc<RefCell<..>>)内。

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

但是,当你有Rc<RefCell<Box<Duck>>>时,rustc需要知道如何构造和解构RefCellRc,以便将相同的胖指针转换应用于其内部。当然,因为这些是图书馆类型,所以它不知道如何去做。对于任何其他包装类型也是如此,例如ArcMutex甚至Vec。您不希望将Vec<Box<Duck>>用作Vec<Box<Quack>>,对吧?

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

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

那就是说,我看到可能如何允许这种选择类型。例如,我们可以引入某种Construct / Unwrap特征,这些特征是编译器已知的,并且可以用于&#34;到达&#34;在一堆包装器内部并在其中执行特征对象转换。然而,没有人设计这个东西并提供了关于它的RFC - 可能是因为它不是一个广泛需要的功能。

答案 1 :(得分:1)

Vladimir's answer解释了什么 编译器正在做。根据这些信息,我开发了一个解决方案:创建一个包装器 结构Box<Quack>

包装器名为QuackWrap。它有固定的尺寸,可以像任何一样使用 其他结构(我认为)。 Box内的QuackWrap允许我构建QuackWrap 围绕任何实现Quack的特征。因此,我可以Vec<Rc<RefCell<QuackWrap>>> 其中内部值是Duck s,Goose s等的混合物

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

trait Quack {
    fn quack(&self);
}

struct Duck;

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

struct QuackWrap(Box<Quack>);

impl QuackWrap {
    pub fn new<T: Quack + 'static>(value: T) -> QuackWrap {
        QuackWrap(Box::new(value))
    }
}

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

    // This would be a type error:
    //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.

    // Instead, we can do this:
    let mut lake: Vec<Rc<RefCell<QuackWrap>>> = Vec::new();
    let mallard: Rc<RefCell<QuackWrap>> = Rc::new(RefCell::new(QuackWrap::new(Duck)));
    lake.push(mallard); // This is valid.
}

为方便起见,我可能希望实施DerefDefrefMut QuackWrap。但对于上面的例子来说,这不是必需的。