std::any::Any 是 Rust 在运行时进行类型擦除和转换的工具,所有 'static 类型都实现了 Any,因此装箱为 Box<dyn Any> 后可以借助 Any::type_id() 获取 TypeId,还可以通过 dyn Any 的 downcast_*() 等方法再转换回具体类型。
然而问题也出在 dyn Any 的 downcast_*() 方法。这些方法是 dyn Any 的方法,而不是 Any trait 中的方法,所以其他任何 dyn Trait 都不会拥有这些方法,即使有 Trait: Any。另一方面,由于 trait upcasting 直到最近才成为稳定特性,且还没进入 stable 版本,所以依赖语言支持的 trait upcasting 来转换为 dyn Any 对于较早版本的项目并不合适。
因此,本文则通过纯 Rust 语法来扩展 trait object,以实现 Any 的所有功能。这些实现都可以在 better-as-any 找到。
现有解决方案
downcast
downcast crate 通过宏来生成代码,直接为指定的 dyn Trait 添加 is()、downcast_*() 方法。这种做法在最终效果上与 dyn Any 完全一样,但是需要使用 impl_downcast!() 宏来实现。我个人则偏好能使用语言特性实现就不使用宏。
as-any
这个 crate 不使用宏实现了功能,其中的 AsAny 可以把任意类型的引用转换为对 dyn Any 的应用,即 trait upcasting。同时其有 Downcast trait 可以把引用向下转型为具体类型的引用。
as-any 的实现方法有着非常严重的缺陷,这个错误并不明显,而且考虑到 Any trait 的应用场景是为了实现动态类型,所以错误会更加难以被发现。
首先是 AsAny 的定义及其实现,任何实现了 Any 的类型都会实现 AsAny:
pub trait AsAny: Any {
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
fn type_name(&self) -> &'static str;
}
impl<T: Any> AsAny for T {
#[inline(always)]
fn as_any(&self) -> &dyn Any {
self
}
#[inline(always)]
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
#[inline(always)]
fn type_name(&self) -> &'static str {
core::any::type_name::<T>()
}
}
然后是 Downcast trait:
pub trait Downcast: AsAny {
#[inline]
fn is<T>(&self) -> bool
where
T: AsAny,
{
self.as_any().is::<T>()
}
#[inline]
fn downcast_ref<T>(&self) -> Option<&T>
where
T: AsAny,
{
self.as_any().downcast_ref()
}
#[inline]
fn downcast_mut<T>(&mut self) -> Option<&mut T>
where
T: AsAny,
{
self.as_any_mut().downcast_mut()
}
}
impl<T: ?Sized + AsAny> Downcast for T {}
所有实现了 AsAny 的类型也会实现 Downcast。那么考虑以下代码:
trait Trait: AsAny {}
impl Trait for i32 {}
let val: Box<dyn Trait> = Box::new(i32);
assert!(val.is<i32>()); // Assertion fails here.
Trait 继承了 AsAny,所以 dyn Trait 会实现 AsAny,也会实现 Downcast,这没有问题。问题在于 Box<dyn Trait> 也实现了 AsAny,也就实现了 Downcast,这种情况下,调用 val.is<i32>() 就会选择 <Box<dyn Trait> as Downcast>::is<i32>(),而不是先解引用再调用 <(dyn Trait) as Downcast>::is<i32>()。
<Box<dyn Trait> as Downcast>::is<i32>() 中 Box<dyn Trait> 不是 trait object,此处的调用自然不会选择 dynamic dispatch,所以只有 Box<dyn Trait> 作为类型参数时才会返回 true。
更好的解决方案
向上转型
首先需要实现向上转型的功能。这里采用与 as-any crate 类似的方法,使用一个 InheritAny trait:
pub trait InheritAny: Any + AsAnyRef + AsAnyMut + IntoAnyBox + IntoAnyRc + IntoAnyArc {
fn type_name(&self) -> &'static str;
}
这个 InheritAny 继承了 AsAnyRef、AsAnyMut 等 trait,它们分别定义了各种向上转型的方法,以 AsAnyRef 为例:
pub trait AsAnyRef: Any {
fn as_any_ref(&self) -> &dyn Any;
fn as_any_ref_send(&self) -> &(dyn Any + Send)
where
Self: Send;
fn as_any_ref_send_sync(&self) -> &(dyn Any + Send + Sync)
where
Self: Send + Sync;
}
AsAnyRef 不仅支持转型为 &dyn Any,还支持 &(dyn Any + Send)、(&dyn Any + Send + Sync)。其他的 trait 也是类似的。
注意到 InheritAny 和其他 trait 都使用了类似以下的 blanket implementation,所以如果在智能指针上直接调用 as_any_ref(),返回的引用会指向智能指针,这点无法避免。
impl<T: Any> InheritAny for T {
fn type_name(&self) -> &'static str {
any::type_name::<T>()
}
}
向下转型
引用的转型
我们需要对所有的具有所有权的智能指针做特殊处理,通过重借用即先解引用再取引用,再向上转型为 dyn Any 的相应类型。所以 Downcast* 的实现对象不再是任意的 T,而应该是 T: Deref。这就导致无法再为任意的 T 提供 Downcast* 的实现,因为 Rust 目前还没有特化,同时实现会导致冲突,但通常情况下,我们都是通过引用或智能指针等 impl Deref 类型访问内部,所以内部是否实现 Downcast* 不是非常重要。
以下是 DowncastRef 的实现:
pub trait DowncastRef {
fn is<T: Any>(&self) -> bool;
fn downcast_ref<T: Any>(&self) -> Option<&T>;
}
impl<S: Deref<Target: AsAnyRef>> DowncastRef for S {
#[inline]
fn is<T: Any>(&self) -> bool {
(**self).as_any_ref().is::<T>()
}
#[inline]
fn downcast_ref<T: Any>(&self) -> Option<&T> {
(**self).as_any_ref().downcast_ref::<T>()
}
}
此外还有 DowncastMut 和 Downcast,分别将可变引用和智能指针向下转型。
GAT 的使用
以下是 Downcast 的定义:
pub trait Downcast: Owned + Sized {
fn downcast<T>(self) -> Result<T::Output, Self>
where
T: Applicable<Self, Output = <Self::Family as OwnedFamily>::Owned<T>>;
}
Downcast 的实现者是各种的智能指针:Box<T>、Rc<T>、Arc<T>,而这些智能指针对转型的目标类型有各自的要求,如 Arc<T> 的转型要求 T 是 Send + Sync 的。(Arc<T> 的创建不一定需要 T: Send + Sync)。所以 Downcast::downcast<T>() 中 T 的 trait bound 还会与智能指针 Self 有关,这样的 double dispatch 问题就需要再引入一个 Applicable<O> trait 来实现。
为了方便表示各种智能指针,首先定义辅助 trait Owned 和 OwnedFamily,Owned 表示一个智能指针及其被包装类型的整体,如 Box<i32>,而 OwnedFamily 则表示一个智能指针的抽象类别,并利用 GAT 来实现类型函数的功能:
pub trait Owned {
type Family: OwnedFamily;
}
pub trait OwnedFamily {
type Owned<T: ?Sized>: Owned<Family = Self>;
}
实现 Owned:
impl<T: ?Sized> Owned for Box<T> {
type Family = BoxFamily;
}
impl<T: ?Sized> Owned for Rc<T> {
type Family = RcFamily;
}
impl<T: ?Sized> Owned for Arc<T> {
type Family = ArcFamily;
}
实现 OwnedFamily:
pub struct BoxFamily;
impl OwnedFamily for BoxFamily {
type Owned<T: ?Sized> = Box<T>;
}
pub struct RcFamily;
impl OwnedFamily for RcFamily {
type Owned<T: ?Sized> = Rc<T>;
}
pub struct ArcFamily;
impl OwnedFamily for ArcFamily {
type Owned<T: ?Sized> = Arc<T>;
}
通过 GAT,我们就可以利用 OwnedFamily::Owned<T> 来表示一个 _<T> 的语义,这里 _ 可以是 Box,Rc 或 Arc。这可以理解为 Box<_>、Rc<_> 和 Arc<_> 是一系列表示智能指针的类型函数(也就是高阶类型,Rust 中并没有办法直接表达),而 OwnedFamily 是各种这些智能指针类型函数的统一的接口。只要通过变换 <S as OwnedFamily>::Owned<T> 中的 S 和 T,就可以组合出任意的 S<T>。
完成实现
接下来就可以定义 Applicable<O> trait 了:
pub trait Applicable<O>: Any + Sized
where
O: Owned,
O::Family: OwnedFamily<Owned<Self> = Self::Output>,
{
type Output;
fn apply_downcasting(owned: O) -> Result<Self::Output, O>;
}
Box 可以这样定义:
impl<S, T> Applicable<Box<S>> for T
where
S: IntoAnyBox + ?Sized,
T: Any,
{
type Output = Box<T>;
fn apply_downcasting(owned: Box<S>) -> Result<Self::Output, Box<S>> {
if owned.is::<T>() {
let res = owned
.into_any_box()
.downcast::<T>()
.unwrap_or_else(|_| std::unreachable!("`self` should be `Box<T>`"));
Ok(res)
} else {
Err(owned)
}
}
}
而 Arc 可以这样定义,注意 trait bound 的区别:
impl<S, T> Applicable<Arc<S>> for T
where
S: IntoAnyArc + Send + Sync + ?Sized,
T: Any + Send + Sync,
{
type Output = Arc<T>;
fn apply_downcasting(owned: Arc<S>) -> Result<Self::Output, Arc<S>> {
if owned.is::<T>() {
let res = owned
.into_any_arc_send_sync()
.downcast::<T>()
.unwrap_or_else(|_| std::unreachable!("`self` should be `Arc<T>`"));
Ok(res)
} else {
Err(owned)
}
}
}
然后只要在 Downcast 的实现中调用 Applicable 的接口就可以了,至此我们完成了所有的功能。