如何从特征对象获取对具体类型的引用?

时间:2015-11-13 07:03:36

标签: rust traits

如何从此代码中的Box<B>变量中获取&B&Box<B>a

trait A {}

struct B;
impl A for B {}

fn main() {
    let mut a: Box<dyn A> = Box::new(B);
    let b = a as Box<B>;
}

此代码返回错误:

error[E0605]: non-primitive cast: `std::boxed::Box<dyn A>` as `std::boxed::Box<B>`
 --> src/main.rs:8:13
  |
8 |     let b = a as Box<B>;
  |             ^^^^^^^^^^^
  |
  = note: an `as` expression can only be used to convert between primitive types. Consider using the `From` trait

2 个答案:

答案 0 :(得分:45)

在Rust中有两种方法可以进行向下转换。第一种是使用Any。请注意,此允许您向下转换为确切的原始具体类型。像这样:

use std::any::Any;

trait A {
    fn as_any(&self) -> &dyn Any;
}

struct B;

impl A for B {
    fn as_any(&self) -> &dyn Any {
        self
    }
}

fn main() {
    let a: Box<dyn A> = Box::new(B);
    // The indirection through `as_any` is because using `downcast_ref`
    // on `Box<A>` *directly* only lets us downcast back to `&A` again.
    // The method ensures we get an `Any` vtable that lets us downcast
    // back to the original, concrete type.
    let b: &B = match a.as_any().downcast_ref::<B>() {
        Some(b) => b,
        None => panic!("&a isn't a B!"),
    };
}

另一种方法是为基本特征(在本例中为A)的每个“目标”实现一个方法,并为每个所需的目标类型实现强制转换。

等等,为什么我们需要as_any

即使您将Any添加为A的要求,它仍然无法正常运行。第一个问题是A中的Box<dyn A> 实施Any ...这意味着当您致电downcast_ref时,您实际上会在对象类型A上调用它。 Any只能 向下广播到它所调用的类型,在这种情况下为A,因此您只能转发回&dyn A你已经拥有了。

但是某处的基础类型有Any的实现,对吗?嗯,是的,但你无法得到它。 Rust不允许您从&dyn A“转换”到&dyn Any

as_any的用途;因为它只是在我们的“具体”类型上实现的,所以编译器不会对它应该调用哪一个感到困惑。在&dyn A上调用它会导致它动态调度到具体实现(在这种情况下,再次,B::as_any),它使用&dyn Any的实现返回Any B,这就是我们想要的。

请注意, 可以通过完全不使用A 来解决整个问题。具体来说,以下工作:

fn main() {
    let a: Box<dyn Any> = Box::new(B);
    let _: &B = match a.downcast_ref::<B>() {
        Some(b) => b,
        None => panic!("&a isn't a B!")
    };    
}

但是,这使您无法使用任何其他方法;你可以在这里做的所有是一个具体的类型。

作为潜在兴趣的最后一点,mopa包可让您将Any的功能与您自己的特征结合起来。

答案 1 :(得分:4)

应该很清楚,如果有另一种类型C实施A并且您尝试将Box<C>投射到Box<B>,则广告可能会失败。我不了解你的情况,但对我来说,它看起来很像你将其他语言(如Java)的技术带入Rust。我从未在Rust中遇到过这种问题 - 也许你的代码设计可以改进以避免这种类型的演员。

如果你愿意,你可以&#34;演员&#34;几乎任何mem::transmute的东西。遗憾的是,如果我们只想将Box<A>转换为Box<B>&A转换为&B,我们就会遇到问题,因为指向trait的指针是胖的实际上由两个指针组成的指针:一个指向实际对象,一个指向vptr。如果我们将其转换为struct类型,我们可以忽略vptr。请记住,这个解决方案是非常不安全和非常hacky - 我不会在&#34;真实&#34;码。

let (b, vptr): (Box<B>, *const ()) = unsafe { std::mem::transmute(a) };

编辑:搞砸了,它比我想象的更不安全。如果您想以这种方式正确地执行此操作,则必须使用std::raw::TraitObject。但这仍然不稳定。我不认为这对OP有用;不要用它!

在这个非常相似的问题中有更好的选择:How to match trait implementors