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

Ale*_*ndr 38 traits rust

如何获得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>;
}
Run Code Online (Sandbox Code Playgroud)

此代码返回错误:

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
Run Code Online (Sandbox Code Playgroud)

DK.*_*DK. 54

有两种方法可以在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!"),
    };
}
Run Code Online (Sandbox Code Playgroud)

另一种方法是为基本特征(在这种情况下A)上的每个"目标"实现一个方法,并为每个所需的目标类型实现强制转换.


等等,我们为什么需要as_any

即使你添加Any了一个要求A,它仍然无法正常工作.第一个问题是,ABox<dyn A>实现Any...这意味着,当你打电话downcast_ref,你实际上是在对象类型调用它A. Any可以低垂到它被调用的类型,在这种情况下A,所以你只能就能够施展回落到&dyn A你已经有了.

但是有一个实施Any的基本类型在那里的某个地方,对不对?嗯,是的,但你无法得到它.锈不你"交叉转换",从允许&dyn A&dyn Any.

as_any是为了什么; 因为它只是在我们的"具体"类型上实现的,所以编译器不会对它应该调用哪一个感到困惑.在on上调用它&dyn A会使它动态地调度到具体的实现(在这种情况下,再次B::as_any),它返回一个&dyn Any使用Anyfor 的实现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!")
    };    
}
Run Code Online (Sandbox Code Playgroud)

但是,这使您无法使用任何其他方法; 你在这里所做的一切都是低调的具体类型.

作为潜在兴趣的最后一点,mopa crate允许您将功能Any与您自己的特征结合起来.

  • 我一整天都在寻找这个答案。有时 Rust 感觉非常违反直觉。 (5认同)
  • 对于下一个 googler:使用 [downcast-rs](https://crates.io/crates/downcast-rs) 箱可能会更容易。 (4认同)
  • 值得指出为什么需要 `as_any` 函数。这是为 `B` 实现的,并采用类型为 `&amp;B` 的参数 `self`,该参数被转换为 `&amp;Any`,稍后可以转换回 `&amp;B`。如果 `a.as_any()` 被替换为 `(&amp;*a as &amp;Any)`,它只能被转换回转换为 `&amp;Any` 的类型,即 `&amp;A`。`&amp;A` 和 `&amp;B` 不是一回事,因为它们有不同的 v-table。 (2认同)

Luk*_*odt 5

应当明确的是,如果C实现了另一种类型A并且您尝试将其Box<C>转换为,转换可能会失败Box<B>。我不知道您的情况,但是对我来说,这很像是您将其他语言(例如Java)的技术引入了Rust。我在Rust中从未遇到过此类问题-也许可以改进您的代码设计以避免这种转换。

如果需要,您可以使用来“投射”几乎所有内容mem::transmute。可悲的是,如果我们只想强制转换Box<A>Box<B>或,则会遇到问题&A&B因为指向traita的指针实际上是一个胖指针,实际上由两个指针组成:一个指向实际对象,一个指向vptr。如果将其强制转换为struct类型,则可以忽略vptr。请记住,此解决方案非常不安全且非常hacky-我不会在“真实”代码中使用它。

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

编辑:拧,那比我想象的还要不安全。如果您想以这种方式正确执行操作,则必须使用std::raw::TraitObject。但是,这仍然不稳定。我认为这对OP没有任何用处;不要使用它!

在这个非常相似的问题中还有更好的选择:如何匹配特征实现者