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

Posted

技术标签:

【中文标题】如何从特征对象中获取对具体类型的引用?【英文标题】:How to get a reference to a concrete type from a trait object? 【发布时间】:2016-02-14 17:19:48 【问题描述】:

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

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

【问题讨论】:

【参考方案1】:

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

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 的要求,它仍然无法正常工作。第一个问题是Box&lt;dyn A&gt; 中的A 实现Any... 这意味着当您调用downcast_ref 时,您实际上会在对象类型@ 上调用它987654334@。 Any 只能向下转换为调用它的类型,在本例中为A,因此您只能向下转换为您已经拥有的&amp;dyn A

但是某处中的底层类型有一个Any 的实现,对吧?嗯,是的,但你无法做到这一点。 Rust 不允许你从&amp;dyn A“交叉转换”到&amp;dyn Any

as_any的用途;因为它只在我们的“具体”类型上实现,所以编译器不会混淆它应该调用哪一个。在&amp;dyn A 上调用它会使其动态分派到具体实现(同样,在本例中为B::as_any),它使用Any 的实现为B 返回一个&amp;dyn Any,这就是我们想要。

请注意,您可以通过不使用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 crate 允许您将Any 的功能与您自己的特征结合起来。

【讨论】:

值得指出为什么需要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-tables。 我一直在寻找这个答案一整天。有时 Rust 感觉非常违反直觉。 对于下一个谷歌用户:使用downcast-rs crate 可能会更容易。【参考方案2】:

应该清楚,如果有另一个类型 C 实现 A 并且您尝试将 Box&lt;C&gt; 转换为 Box&lt;B&gt;,则转换可能会失败。我不知道您的情况,但在我看来,您似乎正在将其他语言(如 Java)的技术引入 Rust。我从来没有在 Rust 中遇到过这种问题——也许你的代码设计可以改进以避免这种类型的转换。

如果您愿意,您可以使用mem::transmute“投射”几乎任何东西。可悲的是,如果我们只想将Box&lt;A&gt; 转换为Box&lt;B&gt;&amp;A 转换为&amp;B,就会遇到问题,因为指向trait 的指针实际上是由两个指针组成的胖指针:一个到实际对象,一个到 vptr。如果我们将其转换为 struct 类型,我们可以忽略 vptr。请记住,这个解决方案非常不安全并且非常hacky——我不会在“真实”代码中使用它。

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

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

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

【讨论】:

感谢您的回答。我正在尝试了解如何应对这种情况。很高兴了解可能的解决方案。

以上是关于如何从特征对象中获取对具体类型的引用?的主要内容,如果未能解决你的问题,请参考以下文章

如何获取对数组中对象的引用[重复]

如何从 sklearn TruncatedSVD 对象中获取特征名称?

如何获取 COM 对象的类型

我可以从 SqlConnection 对象中获取对挂起事务的引用吗?

如何获取对 .nib 文件中对象的引用?

当copylocal属性为false时如何从引用dll中获取类型