英文:
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(&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");
}
The above panics on the expect("2")
. I still can't quite pin my finger on why the type is getting erased. Yes, a
gets upcasted to a Box<dyn Any>
, 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<dyn SomeOtherTrait>
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<u8> = Box::new(0u8);
let a: Box<dyn Any> = a;
let _b = a.as_any().downcast_ref::<Box<u8>>().expect("2");
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 ofBox<dyn Any>
? No. - …a receiver type of
&Box<dyn Any>
? Yes!
See, Box<dyn Any>
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<u8> = Box::new(0u8);
let a: Box<dyn Any> = a;
assert_eq!(TypeId::of::<Box<dyn Any>>(), <dyn Any>::type_id(a.as_any()));
> ...my real question is that I have a Box<dyn SomeOtherTrait>
...
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(&self) -> &dyn Any;
// and also your other important methods
}
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");
}
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<dyn SomeOtherTrait>
to Box<dyn Any>
and &dyn SomeOtherTrait
to &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(&self) -> &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<T: Clone + 'static> ClonableAny for T {} // blanket impl for all applicable types
let data: Box<dyn Any> = data.clone(); // trait upcast (the clone() is for my use case)
let casted_type = *data
.downcast::<C::T>()
.expect("Types should be properly enforced due to generics");
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论