我无法理解代数数据类型中的特征规则。 这是一个简化的例子:
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
相关?
答案 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需要知道如何构造和解构RefCell
和Rc
,以便将相同的胖指针转换应用于其内部。当然,因为这些是图书馆类型,所以它不知道如何去做。对于任何其他包装类型也是如此,例如Arc
或Mutex
甚至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.
}
为方便起见,我可能希望实施Deref
和DefrefMut
QuackWrap
。但对于上面的例子来说,这不是必需的。