意外的恐慌在将 Any 进行向下转型时发生

huangapple go评论69阅读模式
英文:

Unexpected panic when downcasting Any

问题

以下是翻译好的代码部分:

use std::any::Any;

trait AsAny {
    fn as_any(&self) -> &dyn Any;
}
impl<T: Any> AsAny for T {
    fn as_any(&self) -> &dyn Any {
        self
    }
}

fn main() {
    let a: Box<u8> = Box::new(0u8);
    let _b = a.as_any().downcast_ref::<Box<u8>>().expect("1");

    let a: Box<u8> = Box::new(0u8);
    let a: Box<dyn Any> = a;
    let _b = a.as_any().downcast_ref::<Box<u8>>().expect("2");
}

请注意,我已经移除了HTML实体编码,以便代码在文本中显示正确。

英文:

Take the following example:

use std::any::Any;

trait AsAny {
    fn as_any(&amp;self) -&gt; &amp;dyn Any;
}
impl&lt;T: Any&gt; AsAny for T {
    fn as_any(&amp;self) -&gt; &amp;dyn Any {
        self
    }
}

fn main() {
    let a: Box&lt;u8&gt; = Box::new(0u8);
    let _b = a.as_any().downcast_ref::&lt;Box&lt;u8&gt;&gt;().expect(&quot;1&quot;);

    let a: Box&lt;u8&gt; = Box::new(0u8);
    let a: Box&lt;dyn Any&gt; = a;
    let _b = a.as_any().downcast_ref::&lt;Box&lt;u8&gt;&gt;().expect(&quot;2&quot;);
}

The above panics on the expect(&quot;2&quot;). I still can't quite pin my finger on why the type is getting erased. Yes, a gets upcasted to a Box&lt;dyn Any&gt;, but how come it no longer holds the type information so that it can get downcasted later?

Are there any (safe) workarounds for this? I already have a working workaround with Box::into_raw, Box::from_raw, and a pointer cast, but I'd prefer to avoid unsafe.

Edit: To avoid the XY problem, my real question is that I have a Box&lt;dyn SomeOtherTrait&gt; that I need to cast into a SomeConcreteType. I know about the downcast* methods on Box, but unfortunately they aren't much help in my situation.

答案1

得分: 2

以下是代码的翻译部分:

let a: Box<u8> = Box::new(0u8);
let a: Box<dyn Any> = a;
let _b = a.as_any().downcast_ref::<Box<u8>>().expect("2");

为了理解为什么这段代码能够编译通过但不会按您的期望执行,您需要了解 方法解析算法 以及 AsAny 是一个为任何 Sized 类型进行了泛化实现的 trait

a.as_any() 必须解析为某个具体的函数调用。搜索过程如下:

  • 是否存在一个名为 as_any 的方法,其接收者类型为 Box<dyn Any>?没有。
  • …其接收者类型为 &Box<dyn Any>?是的!

您看,Box<dyn Any> 是一个 Sized 类型,因此它实现了 AsAny,而编译器首先尝试的是将其_引用_而不是_解引用_。因此,Box 包含 dyn Any 这一事实从未参与其中。

如果我们小心一点,可以演示这一点:

let a: Box<u8> = Box::new(0u8);
let a: Box<dyn Any> = a;
assert_eq!(TypeId::of::<Box<dyn Any>>(), <dyn Any>::type_id(a.as_any()));

...我的真正问题是我有一个 Box<dyn SomeOtherTrait>...

如果您想从一个 trait 对象进行向下转型,那个 trait 必须提供向下转型的支持。否则,运行时信息就不存在;一个 trait 对象在运行时只有数据指针和 vtable 指针,而 vtable 不包含 TypeId。这个技巧的最简单版本是将 as_any 集成到 trait 中:

use std::any::{TypeId, Any};

trait SomeOtherTrait {
    fn as_any(&self) -> &dyn Any;
    
    // 还有其他重要的方法
}
impl SomeOtherTrait for u8 {
    fn as_any(&self) -> &dyn Any {
        self
    }
}

fn main() {
    let a: Box<u8> = Box::new(0u8);
    let a: Box<dyn SomeOtherTrait> = a;
    assert_eq!(TypeId::of::<u8>(), <dyn Any>::type_id(a.as_any()));
    let _b = a.as_any().downcast_ref::<u8>().expect("2");
}

这些断言会通过。

请注意,如果按照这种方式做事情,对于 SomeOtherTrait,每个实现者都必须具有一个实际的 as_any() 方法;这就是使 SomeOtherTrait vtables 包含所需信息的原因。

在 Rust 的将来版本中,通过 trait 上溯转换,这可能会更简单,您可以简单地声明 trait SomeOtherTrait: Any,然后将 Box<dyn SomeOtherTrait> 强制转换为 Box<dyn Any>&dyn SomeOtherTrait 强制转换为 &dyn SomeOtherTrait

目前,最简单的方法是使用一个库,该库使用更复杂的技巧来为您的 trait 提供方便的向下转型,例如 downcast-rs。(不幸的是,我不太了解它是如何工作的,所以无法提供解释。)这允许您不要求所有的 trait 实现者都包含 fn as_any(&self) -> &dyn Any { self }

(如果 SomeOtherTrait 不是您控制的 trait,那么您无能为力。)

英文:
let a: Box&lt;u8&gt; = Box::new(0u8);
let a: Box&lt;dyn Any&gt; = a;
let _b = a.as_any().downcast_ref::&lt;Box&lt;u8&gt;&gt;().expect(&quot;2&quot;);

In order to understand why this compiles, but does not execute as you expect, you need to understand the method resolution algorithm, and the fact that AsAny is a trait you have blanket implemented for any Sized type.

a.as_any() has to be resolved to some specific function call. The search proceeds as follows:

  • Is there any method named as_any which has a receiver type of Box&lt;dyn Any&gt;? No.
  • …a receiver type of &amp;Box&lt;dyn Any&gt;? Yes!

See, Box&lt;dyn Any&gt; is a Sized type, so it implements AsAny, and the first thing the compiler tries is referencing it, not dereferencing it. Thus, the fact that the Box contains dyn Any never gets involved.

If we're careful we can demonstrate this:

let a: Box&lt;u8&gt; = Box::new(0u8);
let a: Box&lt;dyn Any&gt; = a;
assert_eq!(TypeId::of::&lt;Box&lt;dyn Any&gt;&gt;(), &lt;dyn Any&gt;::type_id(a.as_any()));

> ...my real question is that I have a Box&lt;dyn SomeOtherTrait&gt;...

If you want to be able to downcast from a trait object, that trait must provide downcasting support. Otherwise the run-time information just does not exist; the only information a trait object has at run time is the data pointer and the vtable pointer, and the vtable does not contain a TypeId. The simplest version of the trick is to incorporate as_any into the trait:

use std::any::{TypeId, Any};

trait SomeOtherTrait {
    fn as_any(&amp;self) -&gt; &amp;dyn Any;
    
    // and also your other important methods
}
impl SomeOtherTrait for u8 {
    fn as_any(&amp;self) -&gt; &amp;dyn Any {
        self
    }
}

fn main() {
    let a: Box&lt;u8&gt; = Box::new(0u8);
    let a: Box&lt;dyn SomeOtherTrait&gt; = a;
    assert_eq!(TypeId::of::&lt;u8&gt;(), &lt;dyn Any&gt;::type_id(a.as_any()));
    let _b = a.as_any().downcast_ref::&lt;u8&gt;().expect(&quot;2&quot;);
}

These assertions pass.

Note that if you do things this way, is critical that SomeOtherTrait has an actual as_any() method that each implementor implements; that is what makes SomeOtherTrait vtables contain the information that is necessary.

In future versions of Rust, this may be made simpler by trait upcasting, which might allow you to simply declare trait SomeOtherTrait: Any and then coerce Box&lt;dyn SomeOtherTrait&gt; to Box&lt;dyn Any&gt; and &amp;dyn SomeOtherTrait to &amp;dyn SomeOtherTrait.

For now, the easiest way to get this done is to use a library that uses even more elaborate tricks to provide convenient downcasting attached to your trait, like downcast-rs. (Unfortunately, I don't quite understand how it works so I don't have an explanation to include here.) This allows you to not require all your trait implementors to include fn as_any(&amp;self) -&gt; &amp;dyn Any { self }.

(If SomeOtherTrait is not a trait you control, then there is nothing you can do.)

答案2

得分: 0

感谢Kevin Reid的回答,我能够使用特性向上转型(至少在nightly版本上)来做到这一点(在我的原始问题中,我试图将DynClone转换为具体类型):

trait ClonableAny: Any + DynClone {}          // 定义您自己拥有的继承自Any的自定义特性
dyn_clone::clone_trait_object!(ClonableAny);  // 仅适用于我与DynClone的特定用例的内容
impl<T: Clone + 'static> ClonableAny for T {} // 适用于所有适用类型的通用实现

let data: Box<dyn Any> = data.clone(); // 特性向上转型(clone() 是为了我的用例)
let casted_type = *data
    .downcast::<C::T>()
    .expect("由于泛型应强制执行类型");

关键是创建一个您自己拥有的继承自Any的特性。然后,您可以使用通用实现来为所有具有该特性和其他特性的类型实现它。

英文:

Thanks to Kevin Reid's answer, I was able to use trait upcasting (at least on nightly) to do this (in my original question, I was attempting to cast the DynClone to a concrete type):

trait ClonableAny: Any + DynClone {}          // define custom trait that you own with Any
dyn_clone::clone_trait_object!(ClonableAny);  // something needed for just my particular use case with DynClone
impl&lt;T: Clone + &#39;static&gt; ClonableAny for T {} // blanket impl for all applicable types

let data: Box&lt;dyn Any&gt; = data.clone(); // trait upcast (the clone() is for my use case)
let casted_type = *data
    .downcast::&lt;C::T&gt;()
    .expect(&quot;Types should be properly enforced due to generics&quot;);

The key is to make a trait that you own that inherits from Any. Then you can use a blanket implementation that implements it for all types that have and the other trait.

huangapple
  • 本文由 发表于 2023年6月13日 05:43:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/76460506.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定