代数数据类型中的特征 [英] Traits in algebraic data types

查看:38
本文介绍了代数数据类型中的特征的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm having trouble understanding the rules about traits in algebraic data types. Here's a simplified example:

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.
}

以上无法编译,产生以下错误信息:

The above fails to compile, yielding the following error message:

 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.在前者中,编译器很高兴,但在后者中,则不然.

Why is it that pond.push(duck) is valid, yet lake.push(mallard) isn't? In both cases, a Duck has been supplied where a Quack was expected. In the former, the compiler is happy, but in the latter, it's not.

造成这种差异的原因是否与CoerceUnsized有关?

Is the reason for this difference related to CoerceUnsized?

推荐答案

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

This is a correct behavior, even if it is somewhat unfortunate.

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

In the first case we have this:

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

注意push(),当在Vec>上调用时,接受Box,并且你'正在传递Box.没关系 - rustc 能够理解您想要将装箱值转换为 trait 对象,如下所示:

Note that push(), when called on Vec<Box<Quack>>, accepts Box<Quack>, and you're passing Box<Duck>. This is OK - rustc is able to understand that you want to convert a boxed value to a trait object, like here:

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

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

In the second case we have this:

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> 而你提供 Rc>>:

Here push() accepts Rc<RefCell<Box<Quack>>> while you provide 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 是 DST 兼容类型,因此它可以用作 trait 对象的容器.当 此 RFC 已实施.然而,在这种情况下,没有从具体类型到 trait 对象的强制,因为 Box 位于额外的类型层内(Rc>).

And now there is a trouble. Box<T> is a DST-compatible type, so it can be used as a container for a trait object. The same thing will soon be true for Rc and other smart pointers when this RFC is implemented. However, in this case there is no coercion from a concrete type to a trait object because Box<Duck> is inside of additional layers of types (Rc<RefCell<..>>).

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

Remember, trait object is a fat pointer, so Box<Duck> is different from Box<Quack> in size. Consequently, in principle, they are not directly compatible: you can't just take bytes of Box<Duck> and write them to where Box<Quack> is expected. Rust performs a special conversion, that is, it obtains a pointer to the virtual table for Duck, constructs a fat pointer and writes it to Box<Quack>-typed variable.

当你有 Rc>> 时,rustc 需要知道如何构造和解构 RefCellRc 以便对其内部应用相同的胖指针转换.自然,因为这些是库类型,它不知道该怎么做.对于任何其他包装器类型也是如此,例如ArcMutex 甚至 Vec.您不会期望将 Vec> 用作 Vec> 吧?

When you have Rc<RefCell<Box<Duck>>>, however, rustc would need to know how to construct and destructure both RefCell and Rc in order to apply the same fat pointer conversion to its internals. Naturally, because these are library types, it can't know how to do it. This is also true for any other wrapper type, e.g. Arc or Mutex or even Vec. You don't expect that it would be possible to use Vec<Box<Duck>> as Vec<Box<Quack>>, right?

还有一个事实是,在带有 Rc 的示例中,由 BoxBox 创建的 Rcs 不会'没有连接 - 他们会有不同的引用计数器.

Also there is a fact that in the example with Rc the Rcs created out of Box<Duck> and Box<Quack> wouldn't have been connected - they would have had different reference counters.

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

That is, a conversion from a concrete type to a trait object can only happen if you have direct access to a smart pointer which supports DST, not when it is hidden inside some other structure.

话虽如此,我看到如何可能允许对一些选定类型进行此操作.例如,我们可以引入一些编译器已知的 Construct/Unwrap 特性,它可以用来到达"包装器堆栈内部并执行其中的 trait 对象转换.然而,还没有人设计这个东西并提供有关它的 RFC - 可能是因为它不是一个广泛需要的功能.

That said, I see how it may be possible to allow this for a few select types. For example, we could introduce some kind of Construct/Unwrap traits which are known to the compiler and which it could use to "reach" inside of a stack of wrappers and perform trait object conversion inside them. However, no one designed this thing and provided an RFC about it yet - probably because it is not a widely needed feature.

这篇关于代数数据类型中的特征的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆