October 01, 2025

MultiAny: Downcasting to Multiple Trait Objects in Rust

The Question

In the Reddit comments to my post, the user mio991 asked:

Good, you got it working. But how do I store a value in a way to get any trait object implemented?

As I see it, the task is to store a value in a type-erased way and be able to restore not just one trait as a reference to a trait object, but any implemented trait.

Naive Approach

I doubt it is possible in exactly this way, but if we, for example, just list all implemented traits for an object, then it is doable:

// pseudocode
// SomeStruct should implement Trait1, Trait2, and Trait3 for this to work.
#[derive(MultiAny)]
#[multi_any(Trait1, Trait2, Trait3)]
struct SomeStruct {}
// and later
let dyn_value = MultiAny::new(SomeStruct{});
dyn_value.downcast_ref::<dyn Trait1>();
dyn_value.downcast_ref::<dyn Trait2>();

This is entirely possible to implement using ideas from the previous post. The only difference is that you not only store one piece of metadata, but a list of (TypeId, metadata) pairs. I’ve actually implemented this: Rust Playground. Do not expect anything production-ready; it is just a proof of concept, and a dismissed one.

This would not be worth a new blog post though, as there were no new ideas in it compared to the previous post.

Learning from the Any Trait

Looking at the code, I was thinking: should it be that complicated? Do we need to manually manage memory allocation, implement Drop, and hope it all works as intended? There is an example in the standard library that is worth studying: the Any trait.

It turns out Any is just a normal trait, like any other. It has only one function, type_id, and also has a blanket implementation for all supported types:

pub trait Any: 'static {
fn type_id(&self) -> TypeId;
}
impl<T: 'static + ?Sized> Any for T {
fn type_id(&self) -> TypeId {
TypeId::of::<T>()
}
}

And actually, that is all that is needed to implement downcasting:

// this is not the actual code from the standard library, but it works the same
impl dyn Any {
pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
if TypeId::of::<T>() == self.type_id() {
unsafe { &*(self as *const dyn Any as *const T) }
} else {
None
}
}
}

This works because a pointer to a dyn Trait, including dyn Any, is actually a fat pointer consisting of the data pointer and metadata. All the code in downcast_ref does is discard the metadata if the type IDs match.

MultiAny Concept

Can we utilize a similar approach for downcasting to multiple trait objects? To downcast to a trait object, we need to not just discard the metadata, but replace it with the correct metadata. So our trait can look like this:

pub trait MultiAny {
// Returns metadata if the trait is implemented
// `type_id` is for the type of the trait object `dyn Trait`
fn get_metadata(&self, type_id: TypeId) -> Option<usize>;
}

Here is how we can use it:

impl dyn MultiAny {
pub fn downcast_ref<T>(&self) -> Option<&T>
where
T: Pointee<Metadata = DynMetadata<T>> + ?Sized + 'static,
{
// Get metadata for the requested type ID and convert it to DynMetadata
let meta_raw = self.get_metadata(TypeId::of::<T>())?;
let meta: DynMetadata<T> = unsafe { transmute(meta_raw) };
// Construct a fat pointer by combining the raw data pointer with metadata
let ptr: *const T = from_raw_parts(self as *const dyn MultiAny as *const (), meta);
unsafe { &*ptr }.into()
}
}
impl MultiAny for Foo {
fn get_metadata(&self, type_id: TypeId) -> Option<usize> {
// Return metadata for the requested trait object if it is implemented, or None
if type_id == TypeId::of::<dyn Trait1>() {
let trait_ptr = self as &dyn Trait1 as *const dyn Trait1;
let meta = metadata(trait_ptr);
let meta_raw: usize = unsafe { transmute(meta) };
Some(meta_raw)
}
// Check other implemented traits here
else {
None
}
}
}
let foo: Box<dyn MultiAny> = Box::new(Foo { name: "Bob".into() });
assert_eq!(foo.downcast_ref::<dyn Trait1>().unwrap().hello(), "Hello, Bob");
Full source code for reference
#![feature(ptr_metadata)]
use std::{
any::TypeId,
mem::transmute,
ptr::{DynMetadata, Pointee, from_raw_parts},
};
pub trait MultiAny {
fn get_metadata(&self, type_id: TypeId) -> Option<usize>;
}
impl dyn MultiAny {
// T is a dyn Trait here
pub fn downcast_ref<T>(&self) -> Option<&T>
where
T: Pointee<Metadata = DynMetadata<T>> + ?Sized + 'static,
{
// get metadata for type_id and convert it to DynMetadata
let meta_raw = self.get_metadata(TypeId::of::<T>())?;
let meta: DynMetadata<T> = unsafe { transmute(meta_raw) };
// make fat pointer by combining raw data pointer with metadata
let ptr: *const T = from_raw_parts(self as *const dyn MultiAny as *const (), meta);
unsafe { &*ptr }.into()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::ptr::metadata;
trait Trait1 {
fn hello(&self) -> String;
}
trait Trait2 {
fn bye(&self) -> String;
}
trait Trait3 {}
struct Foo {
name: String,
}
impl Trait1 for Foo {
fn hello(&self) -> String {
format!("Hello, {}", self.name)
}
}
impl Trait2 for Foo {
fn bye(&self) -> String {
format!("Bye, {}", self.name)
}
}
impl MultiAny for Foo {
fn get_metadata(&self, type_id: TypeId) -> Option<usize> {
if type_id == TypeId::of::<dyn Trait1>() {
let trait_ptr = self as &dyn Trait1 as *const dyn Trait1;
let meta = metadata(trait_ptr);
let meta_raw: usize = unsafe { transmute(meta) };
Some(meta_raw)
} else if type_id == TypeId::of::<dyn Trait2>() {
let trait_ptr = self as &dyn Trait2 as *const dyn Trait2;
let meta = metadata(trait_ptr);
let meta_raw: usize = unsafe { transmute(meta) };
Some(meta_raw)
} else {
None
}
}
}
#[test]
fn test() {
let foo: Box<dyn MultiAny> = Box::new(Foo { name: "Bob".into() });
assert_eq!(
foo.downcast_ref::<dyn Trait1>().unwrap().hello(),
"Hello, Bob"
);
assert_eq!(foo.downcast_ref::<dyn Trait2>().unwrap().bye(), "Bye, Bob");
assert!(foo.downcast_ref::<dyn Trait3>().is_none());
}
}

Safety issues

As a proof of concept this code works. From an ergonomics perspective, it only needs a derive macro. But from a safety perspective, it is unsound: it is very easy to implement MultiAny incorrectly:

impl MultiAny for Foo {
fn get_metadata(&self, type_id: TypeId) -> Option<usize> {
// when asked for metadata for the Trait1 return metadata for Trait2
if type_id == TypeId::of::<dyn Trait1>() {
let trait_ptr = self as &dyn Trait2 as *const dyn Trait2;
let meta = metadata(trait_ptr);
let meta_raw: usize = unsafe { transmute(meta) };
Some(meta_raw)
}
else {
None
}
}
}

The code above will return metadata for Trait2 when asked for metadata for Trait1. There are no further checks, so the code in downcast_ref will just interpret DynMetadata<dyn Trait2> as DynMetadata<dyn Trait1> and return the reference. This will lead to undefined behavior.

Type-Safe Metadata

We need to make sure that returned metadata actually matches the requested type_id (which refers to dyn Trait). We can do this by making get_metadata return a type, that guarantees that type_id matches the metadata:

// type representing type-erased metadata
pub struct Meta {
meta_raw: usize,
type_id: TypeId,
}
impl Meta {
// The only way to construct `Meta` is through this function.
// This guarantees that the stored metadata always matches the stored type ID.
pub fn try_from_trait<DynTrait>(t: &DynTrait, type_id: TypeId) -> Option<Meta> {
if type_id != TypeId::of::<DynTrait>() { return None; }
let meta = metadata(t);
let meta_raw: usize = unsafe { transmute(meta) };
Some(Meta { meta_raw, type_id })
}
// This function checks that the stored type ID matches the type we are converting to.
// It is impossible to convert `Meta` into the wrong `DynMetadata`.
pub fn into_metadata<DynTrait>(self) -> DynTrait::Metadata {
assert_eq!(self.type_id, TypeId::of::<DynTrait>(), "Wrong dyn Trait type");
let meta: DynMetadata<DynTrait> = unsafe { transmute(self.meta_raw) };
meta
}
}
// `get_metadata` now returns `Meta`
pub trait MultiAny {
fn get_metadata(&self, type_id: TypeId) -> Option<Meta>;
}
// `downcast_ref` uses `Meta` to safely convert metadata
impl dyn MultiAny {
pub fn downcast_ref<T>(&self) -> Option<&T> {
let meta = self.get_metadata(TypeId::of::<T>())?.into_metadata::<T>();
let ptr: *const T = from_raw_parts(self as *const dyn MultiAny as *const (), meta);
unsafe { &*ptr }.into()
}
}
Full source code for reference
#![feature(ptr_metadata)]
use std::{
any::TypeId,
mem::transmute,
ptr::{DynMetadata, Pointee, from_raw_parts, metadata},
};
pub struct Meta {
meta_raw: usize,
type_id: TypeId,
}
impl Meta {
pub fn try_from_trait<DynTrait>(t: &DynTrait, type_id: TypeId) -> Option<Meta>
where
DynTrait: Pointee<Metadata = DynMetadata<DynTrait>> + ?Sized + 'static,
{
if type_id != TypeId::of::<DynTrait>() {
return None;
}
let meta = metadata(t);
// SAFETY: We transmute DynMetadata<T> to usize.
// This safe because we will only use resulting usize to transmute back to exact same type.
// Note: transmute guarantees that types has the same size.
let meta_raw: usize = unsafe { transmute(meta) };
Some(Meta { meta_raw, type_id })
}
pub fn into_metadata<DynTrait>(self) -> DynTrait::Metadata
where
DynTrait: Pointee<Metadata = DynMetadata<DynTrait>> + ?Sized + 'static,
{
assert_eq!(
self.type_id,
TypeId::of::<DynTrait>(),
"Wrong dyn Trait type"
);
// SAFETY: We transmute usize to DynMetadata<T>.
// This safe because we are sure that usize was aquired by transmuting exact same type.
// Note: transmute guarantees that types has the same size.
let meta: DynMetadata<DynTrait> = unsafe { transmute(self.meta_raw) };
meta
}
}
pub trait MultiAny {
fn get_metadata(&self, type_id: TypeId) -> Option<Meta>;
}
impl dyn MultiAny {
pub fn downcast_ref<T>(&self) -> Option<&T>
where
T: Pointee<Metadata = DynMetadata<T>> + ?Sized + 'static,
{
let meta = self.get_metadata(TypeId::of::<T>())?.into_metadata::<T>();
let ptr: *const T = from_raw_parts(self as *const dyn MultiAny as *const (), meta);
// SAFETY: We are converting raw pointer to reference.
// This is safe because pointer points to the same data as self.
unsafe { &*ptr }.into()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic() {
trait Hello {
fn hello(&self) -> String;
}
trait Bye {
fn bye(&self) -> String;
}
trait Other {}
struct Foo {
name: String,
}
impl Hello for Foo {
fn hello(&self) -> String {
format!("Hello, {}", self.name)
}
}
impl Bye for Foo {
fn bye(&self) -> String {
format!("Bye, {}", self.name)
}
}
impl MultiAny for Foo {
fn get_metadata(&self, type_id: TypeId) -> Option<Meta> {
Meta::try_from_trait(self as &dyn Hello, type_id)
.or_else(|| Meta::try_from_trait(self as &dyn Bye, type_id))
}
}
let foo: Box<dyn MultiAny> = Box::new(Foo { name: "Bob".into() });
assert_eq!(
foo.downcast_ref::<dyn Hello>().unwrap().hello(),
"Hello, Bob"
);
assert_eq!(foo.downcast_ref::<dyn Bye>().unwrap().bye(), "Bye, Bob");
assert!(foo.downcast_ref::<dyn Other>().is_none());
}
#[test]
fn test_wrong_dyn_trait_type() {
trait Trait {}
trait WrongTrait {}
struct Bar;
impl Trait for Bar {}
impl WrongTrait for Bar {}
impl MultiAny for Bar {
fn get_metadata(&self, _type_id: TypeId) -> Option<Meta> {
// try to use type_id not matching the supplied reference
Meta::try_from_trait(self as &dyn Trait, TypeId::of::<dyn WrongTrait>())
}
}
let bar: Box<dyn MultiAny> = Box::new(Bar);
assert!(bar.downcast_ref::<dyn Trait>().is_none());
assert!(bar.downcast_ref::<dyn WrongTrait>().is_none());
}
#[test]
#[should_panic = "Wrong dyn Trait type"]
fn test_wrong_dyn_trait_type_panic() {
trait Trait {}
trait WrongTrait {}
struct Bar;
impl Trait for Bar {}
impl WrongTrait for Bar {}
impl MultiAny for Bar {
fn get_metadata(&self, _type_id: TypeId) -> Option<Meta> {
// return metadata for WrongTrait regardless of what was requested
Meta::try_from_trait(self as &dyn WrongTrait, TypeId::of::<dyn WrongTrait>())
}
}
let bar: Box<dyn MultiAny> = Box::new(Bar);
bar.downcast_ref::<dyn Trait>();
}
}

The only way to construct Meta is through try_from_trait, which guarantees that the stored metadata corresponds to the provided type. When converting Meta back into DynMetadata<T>, the type ID is validated, ensuring that we cannot mistakenly convert DynMetadata<T> into DynMetadata<K> for a different type K.

Data Type Validation

There is still a way to implement MultiAny incorrectly so that it will lead to undefined behavior. We can return metadata that matches the trait object, but does not match the data type:

trait Trait {}
struct Bar;
struct Baz;
impl Trait for Bar {}
impl Trait for Baz {}
impl MultiAny for Bar {
fn get_metadata(&self, type_id: TypeId) -> Option<Meta> {
// returning metadata for right dyn Trait but wrong data type
Meta::try_from_trait(&Baz as &dyn Trait, type_id)
}
}

Here get_metadata returns the right type DynMetadata<dyn Trait>, but the vtable is for Trait implemented for Baz, not Bar.

To fix this, we can store type_id for the data type with meta. Then downcast_ref can assert that data_id matches self.type_id(), preventing undefined behavior:

// `Meta` stores two type IDs:
// - `data_id`: the concrete type of the underlying value
// - `dyn_trait_id`: the trait object type (`dyn Trait`) this metadata belongs to
pub struct Meta {
meta_raw: usize,
data_id: TypeId,
dyn_trait_id: TypeId,
}
impl Meta {
// `try_from_trait` takes a reference to the data and a cast function that produces the trait object.
// This avoids invalid combinations of type IDs by tying the metadata to an actual cast.
pub fn try_from_trait<Data, DynTrait>(
data: &Data,
requested_trait_id: TypeId,
cast_fn: fn(&Data) -> &DynTrait,
) -> Option<Meta> { ... }
// `into_metadata` validates only the trait object type (`dyn_trait_id`).
// It cannot verify the underlying data pointer, but the stored `data_id` is consistent with the original data.
pub fn into_metadata<DynTrait>(self) -> DynTrait::Metadata { ... }
}
// `MultiAny` extends `Any` to provide access to the concrete type ID of the stored value.
pub trait MultiAny: Any {
fn get_metadata(&self, type_id: TypeId) -> Option<Meta>;
}
impl dyn MultiAny {
// `downcast_ref` checks that the underlying data type matches before reconstructing the pointer.
pub fn downcast_ref<DynTrait>(&self) -> Option<&DynTrait> {
let meta = self.get_metadata(TypeId::of::<DynTrait>())?;
// Verify the stored `data_id` via `Any::type_id()`.
assert_eq!(meta.data_id, self.type_id(), "Wrong Data type");
...
}
}
Full source code for reference
#![feature(ptr_metadata)]
use std::{
any::{Any, TypeId},
mem::transmute,
ptr::{DynMetadata, Pointee, from_raw_parts, metadata},
};
pub struct Meta {
meta_raw: usize,
data_id: TypeId,
dyn_trait_id: TypeId,
}
impl Meta {
/// If `requested_trait_id` matches `DynTrait` extract pointer metadata for `DynTrait`
pub fn try_from_trait<Data, DynTrait>(
data: &Data,
requested_trait_id: TypeId,
cast_fn: fn(&Data) -> &DynTrait,
) -> Option<Meta>
where
Data: 'static,
DynTrait: Pointee<Metadata = DynMetadata<DynTrait>> + ?Sized + 'static,
{
if requested_trait_id != TypeId::of::<DynTrait>() {
return None;
}
let dyn_trait = cast_fn(data);
let meta = metadata(dyn_trait);
// SAFETY: We transmute DynMetadata<T> to usize.
// This safe because we will only use resulting usize to transmute back to exact same type.
// Note: transmute guarantees that types has the same size.
let meta_raw: usize = unsafe { transmute(meta) };
Some(Meta {
meta_raw,
data_id: TypeId::of::<Data>(),
dyn_trait_id: requested_trait_id,
})
}
/// Convert into concrete pointer metadata
pub fn into_metadata<DynTrait>(self) -> DynTrait::Metadata
where
DynTrait: Pointee<Metadata = DynMetadata<DynTrait>> + ?Sized + 'static,
{
assert_eq!(
self.dyn_trait_id,
TypeId::of::<DynTrait>(),
"Wrong dyn Trait type"
);
// SAFETY: We transmute usize to DynMetadata<T>.
// This safe because we are sure that usize was aquired by transmuting exact same type.
// Note: transmute guarantees that types has the same size.
let meta: DynMetadata<DynTrait> = unsafe { transmute(self.meta_raw) };
meta
}
}
pub trait MultiAny: Any {
fn get_metadata(&self, type_id: TypeId) -> Option<Meta>;
}
impl dyn MultiAny {
pub fn downcast_ref<DynTrait>(&self) -> Option<&DynTrait>
where
DynTrait: Pointee<Metadata = DynMetadata<DynTrait>> + ?Sized + 'static,
{
let meta = self.get_metadata(TypeId::of::<DynTrait>())?;
assert_eq!(meta.data_id, self.type_id(), "Wrong Data type");
// SAFETY: We are merging raw data pointer with metadata.
// This is safe because we know that data type matches the type this metadata was created for.
let ptr: *const DynTrait = from_raw_parts(
self as *const dyn MultiAny as *const (),
meta.into_metadata::<DynTrait>(),
);
// SAFETY: We are converting raw pointer to reference.
// This is safe because pointer points to the same data as self.
unsafe { &*ptr }.into()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic() {
trait Hello {
fn hello(&self) -> String;
}
trait Bye {
fn bye(&self) -> String;
}
trait Other {}
struct Foo {
name: String,
}
impl Hello for Foo {
fn hello(&self) -> String {
format!("Hello, {}", self.name)
}
}
impl Bye for Foo {
fn bye(&self) -> String {
format!("Bye, {}", self.name)
}
}
impl MultiAny for Foo {
fn get_metadata(&self, type_id: TypeId) -> Option<Meta> {
Meta::try_from_trait(self, type_id, |v| v as &dyn Hello)
.or_else(|| Meta::try_from_trait(self, type_id, |v| v as &dyn Bye))
}
}
let foo: Box<dyn MultiAny> = Box::new(Foo { name: "Bob".into() });
assert_eq!(
foo.downcast_ref::<dyn Hello>().unwrap().hello(),
"Hello, Bob"
);
assert_eq!(foo.downcast_ref::<dyn Bye>().unwrap().bye(), "Bye, Bob");
assert!(foo.downcast_ref::<dyn Other>().is_none());
}
#[test]
fn test_wrong_dyn_trait_type() {
trait Trait {}
trait WrongTrait {}
struct Bar;
impl Trait for Bar {}
impl WrongTrait for Bar {}
impl MultiAny for Bar {
fn get_metadata(&self, type_id: TypeId) -> Option<Meta> {
_ = type_id;
// try to use type_id not matching the supplied reference
Meta::try_from_trait(self, TypeId::of::<dyn WrongTrait>(), |v| v as &dyn Trait)
}
}
let bar: Box<dyn MultiAny> = Box::new(Bar);
assert!(bar.downcast_ref::<dyn Trait>().is_none());
assert!(bar.downcast_ref::<dyn WrongTrait>().is_none());
}
#[test]
#[should_panic = "Wrong dyn Trait type"]
fn test_wrong_dyn_trait_type_panic() {
trait Trait {}
trait WrongTrait {}
struct Bar;
impl Trait for Bar {}
impl WrongTrait for Bar {}
impl MultiAny for Bar {
fn get_metadata(&self, type_id: TypeId) -> Option<Meta> {
_ = type_id;
// return metadata for WrongTrait regardless of what was requested
Meta::try_from_trait(self, TypeId::of::<dyn WrongTrait>(), |v| {
v as &dyn WrongTrait
})
}
}
let bar: Box<dyn MultiAny> = Box::new(Bar);
bar.downcast_ref::<dyn Trait>();
}
#[test]
#[should_panic = "Wrong Data type"]
fn test_wrong_data_type_panic() {
trait Trait {}
struct Bar;
struct Baz;
impl Trait for Bar {}
impl Trait for Baz {}
impl MultiAny for Bar {
fn get_metadata(&self, type_id: TypeId) -> Option<Meta> {
// returning metadata for right dyn Trait but wrong data type
Meta::try_from_trait(&Baz, type_id, |v| v as &dyn Trait)
}
}
let bar: Box<dyn MultiAny> = Box::new(Bar);
bar.downcast_ref::<dyn Trait>();
}
}

With these checks in place, an incorrect implementation of MultiAny::get_metadata will cause a panic rather than triggering undefined behavior.

Downcasting to Concrete Types

It would be nice to be able to downcast to the original type. At first glance, it looks like it would be quite easy to implement with just adding one extra call to Meta::try_from_trait in the get_metadata:

impl MultiAny for Foo {
fn get_metadata(&self, type_id: TypeId) -> Option<Meta> {
Meta::try_from_trait(self, type_id, |v| v)
.or_else(|| Meta::try_from_trait(self, type_id, |v| v as &dyn Hello))
.or_else(|| Meta::try_from_trait(self, type_id, |v| v as &dyn Bye))
}
}

But this will not compile directly, because concrete types and trait objects have different metadata types. To solve this, we introduce a TypedMetadata trait and implement it for both () (concrete types) and DynMetadata<T> (trait objects), allowing uniform conversion between type-erased Meta and typed metadata.

With this, we can finally downcast a Box<dyn MultiAny> to both trait objects and concrete types safely:

// A trait for converting between `Meta` (type-erased) and typed metadata.
pub trait TypedMetadata {
fn from_meta(meta: Meta) -> Self;
fn into_meta<Data: 'static>(self) -> Meta;
}
impl TypedMetadata for () {
...
}
impl<T> TypedMetadata for DynMetadata<T> {
...
}
// `Meta` now relies on `TypedMetadata` for conversions between typed and type-erased forms.
pub struct Meta { ... }
impl Meta {
pub fn try_from<Data, RequestedType>(
data: &Data,
requested_type_id: TypeId,
cast_fn: fn(&Data) -> &RequestedType,
) -> Option<Meta>
where
RequestedType: Pointee + ?Sized + 'static,
RequestedType::Metadata: TypedMetadata, // Note this constraint
{
...
RequestedType::Metadata::into_meta::<Data>(typed_meta).into()
}
pub fn into_metadata<Data, RequestedType>(self) -> RequestedType::Metadata
where
Data: 'static,
RequestedType: Pointee + ?Sized + 'static,
RequestedType::Metadata: TypedMetadata,
{
assert_eq!(self.data_id, TypeId::of::<Data>(), "Wrong Data type");
RequestedType::Metadata::from_meta(self)
}
}
// Example: successfully downcasting back to `Foo`.
let foo: Box<dyn MultiAny> = Box::new(Foo { name: "Bob".into() });
assert_eq!(foo.downcast_ref::<Foo>().unwrap().name, "Bob");
Full source code for reference
#![feature(ptr_metadata)]
use std::{
any::{Any, TypeId},
mem::transmute,
ptr::{DynMetadata, Pointee, from_raw_parts, metadata},
};
pub trait TypedMetadata {
fn from_meta(meta: Meta) -> Self;
fn into_meta<Data: 'static>(self) -> Meta;
}
impl TypedMetadata for () {
fn from_meta(meta: Meta) -> Self {
assert_eq!(meta.meta_id, TypeId::of::<()>(), "Wrong Metadata type");
}
fn into_meta<Data: 'static>(self) -> Meta {
Meta {
meta_raw: 0,
data_id: TypeId::of::<Data>(),
meta_id: TypeId::of::<Self>(),
}
}
}
impl<T> TypedMetadata for DynMetadata<T>
where
T: Pointee<Metadata = Self> + ?Sized + 'static,
{
fn from_meta(meta: Meta) -> Self {
assert_eq!(meta.meta_id, TypeId::of::<Self>(), "Wrong Metadata type");
// SAFETY: We transmute usize to DynMetadata<T>.
// This safe because we are sure that usize was aquired by transmuting exact same type.
// Note: transmute guarantees that types has the same size.
let typed_meta: Self = unsafe { transmute(meta.meta_raw) };
typed_meta
}
fn into_meta<Data: 'static>(self) -> Meta {
// SAFETY: We transmute DynMetadata<T> to usize.
// This safe because we will only use resulting usize to transmute back to exact same type.
// Note: transmute guarantees that types has the same size.
let meta_raw: usize = unsafe { transmute(self) };
Meta {
meta_raw,
data_id: TypeId::of::<Data>(),
meta_id: TypeId::of::<Self>(),
}
}
}
pub struct Meta {
meta_raw: usize,
data_id: TypeId,
meta_id: TypeId,
}
impl Meta {
/// If `requested_type_id` matches `RequestedType` extract pointer metadata for RequestedType`
pub fn try_from<Data, RequestedType>(
data: &Data,
requested_type_id: TypeId,
cast_fn: fn(&Data) -> &RequestedType,
) -> Option<Meta>
where
Data: 'static,
RequestedType: Pointee + ?Sized + 'static,
RequestedType::Metadata: TypedMetadata,
{
if requested_type_id != TypeId::of::<RequestedType>() {
return None;
}
let dyn_trait = cast_fn(data);
let typed_meta = metadata(dyn_trait);
RequestedType::Metadata::into_meta::<Data>(typed_meta).into()
}
/// Convert into concrete pointer metadata
pub fn into_metadata<Data, RequestedType>(self) -> RequestedType::Metadata
where
Data: 'static,
RequestedType: Pointee + ?Sized + 'static,
RequestedType::Metadata: TypedMetadata,
{
assert_eq!(self.data_id, TypeId::of::<Data>(), "Wrong Data type");
RequestedType::Metadata::from_meta(self)
}
}
pub trait MultiAny: Any {
fn get_metadata(&self, type_id: TypeId) -> Option<Meta>;
}
impl dyn MultiAny {
pub fn downcast_ref<RequestedType>(&self) -> Option<&RequestedType>
where
RequestedType: Pointee + ?Sized + 'static,
RequestedType::Metadata: TypedMetadata,
{
let meta = self.get_metadata(TypeId::of::<RequestedType>())?;
assert_eq!(meta.data_id, self.type_id(), "Wrong Data type");
let typed_meta = RequestedType::Metadata::from_meta(meta);
let data_pointer = self as *const dyn MultiAny as *const ();
// SAFETY: We are merging raw data pointer with metadata.
// This is safe because we know that data type matches the type this metadata was created for.
let ptr: *const RequestedType = from_raw_parts(data_pointer, typed_meta);
// SAFETY: We are converting raw pointer to reference.
// This is safe because pointer points to the same data as self.
unsafe { &*ptr }.into()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic() {
trait Hello {
fn hello(&self) -> String;
}
trait Bye {
fn bye(&self) -> String;
}
trait Other {}
struct Foo {
name: String,
}
impl Hello for Foo {
fn hello(&self) -> String {
format!("Hello, {}", self.name)
}
}
impl Bye for Foo {
fn bye(&self) -> String {
format!("Bye, {}", self.name)
}
}
impl MultiAny for Foo {
fn get_metadata(&self, type_id: TypeId) -> Option<Meta> {
Meta::try_from(self, type_id, |v| v)
.or_else(|| Meta::try_from(self, type_id, |v| v as &dyn Hello))
.or_else(|| Meta::try_from(self, type_id, |v| v as &dyn Bye))
}
}
let foo: Box<dyn MultiAny> = Box::new(Foo { name: "Bob".into() });
// downcast to concrete type
assert_eq!(foo.downcast_ref::<Foo>().unwrap().name, "Bob");
// downcast to dyn Traits that are implemented
assert_eq!(
foo.downcast_ref::<dyn Hello>().unwrap().hello(),
"Hello, Bob"
);
assert_eq!(foo.downcast_ref::<dyn Bye>().unwrap().bye(), "Bye, Bob");
// downcast to dyn Trait that is not implemented
assert!(foo.downcast_ref::<dyn Other>().is_none());
// downcast to wrong type
assert!(foo.downcast_ref::<()>().is_none());
}
#[test]
fn test_wrong_dyn_trait_type() {
trait Trait {}
trait WrongTrait {}
struct Bar;
impl Trait for Bar {}
impl WrongTrait for Bar {}
impl MultiAny for Bar {
fn get_metadata(&self, type_id: TypeId) -> Option<Meta> {
_ = type_id;
// try to use type_id not matching the supplied reference
Meta::try_from(self, TypeId::of::<dyn WrongTrait>(), |v| v as &dyn Trait)
}
}
let bar: Box<dyn MultiAny> = Box::new(Bar);
assert!(bar.downcast_ref::<dyn Trait>().is_none());
assert!(bar.downcast_ref::<dyn WrongTrait>().is_none());
}
#[test]
#[should_panic = "Wrong Metadata type"]
fn test_wrong_dyn_trait_type_panic() {
trait Trait {}
trait WrongTrait {}
struct Bar;
impl Trait for Bar {}
impl WrongTrait for Bar {}
impl MultiAny for Bar {
fn get_metadata(&self, type_id: TypeId) -> Option<Meta> {
_ = type_id;
// return metadata for WrongTrait regardless of what was requested
Meta::try_from(self, TypeId::of::<dyn WrongTrait>(), |v| {
v as &dyn WrongTrait
})
}
}
let bar: Box<dyn MultiAny> = Box::new(Bar);
bar.downcast_ref::<dyn Trait>();
}
#[test]
#[should_panic = "Wrong Data type"]
fn test_wrong_data_type_panic() {
trait Trait {}
struct Bar;
struct Baz;
impl Trait for Bar {}
impl Trait for Baz {}
impl MultiAny for Bar {
fn get_metadata(&self, type_id: TypeId) -> Option<Meta> {
// returning metadata for right dyn Trait but wrong data type
Meta::try_from(&Baz, type_id, |v| v as &dyn Trait)
}
}
let bar: Box<dyn MultiAny> = Box::new(Bar);
bar.downcast_ref::<dyn Trait>();
}
}

With this change, a Box<dyn MultiAny> can downcast either to any implemented trait object or directly to the concrete type of the stored value.

What’s Next?

To make this approach fully practical, a few additional features would be helpful:

I haven’t covered these here, but they are implemented in the MultiAny crate. Thanks to the ptr_meta library, the crate can work on stable Rust, not just nightly.

Thanks for joining me on this little coding adventure.

Discussion

Join the discussion on Reddit.