std::any::Any 是 Rust 在运行时进行类型擦除和转换的工具,所有 'static 类型都实现了 Any,因此装箱为 Box<dyn Any> 后可以借助 Any::type_id() 获取 TypeId,还可以通过 dyn Anydowncast_*() 等方法再转换回具体类型。

然而问题也出在 dyn Anydowncast_*() 方法。这些方法是 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 继承了 AsAnyRefAsAnyMut 等 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>()
    }
}

此外还有 DowncastMutDowncast,分别将可变引用和智能指针向下转型。

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> 的转型要求 TSend + Sync 的。(Arc<T> 的创建不一定需要 T: Send + Sync)。所以 Downcast::downcast<T>()T 的 trait bound 还会与智能指针 Self 有关,这样的 double dispatch 问题就需要再引入一个 Applicable<O> trait 来实现。

为了方便表示各种智能指针,首先定义辅助 trait OwnedOwnedFamilyOwned 表示一个智能指针及其被包装类型的整体,如 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> 的语义,这里 _ 可以是 BoxRcArc。这可以理解为 Box<_>Rc<_>Arc<_> 是一系列表示智能指针的类型函数(也就是高阶类型,Rust 中并没有办法直接表达),而 OwnedFamily 是各种这些智能指针类型函数的统一的接口。只要通过变换 <S as OwnedFamily>::Owned<T> 中的 ST,就可以组合出任意的 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 的接口就可以了,至此我们完成了所有的功能。