[![docs.rs](https://docs.rs/cast_trait_object/badge.svg)](https://docs.rs/cast_trait_object) [![crates.io](https://img.shields.io/crates/v/cast_trait_object.svg)](https://crates.io/crates/cast_trait_object) # cast_trait_object This crate offers functionality for casting between trait objects using only safe Rust and no platform specific code. If you want to downcast to concrete types instead of other trait objects then this crate can't help you, instead use [`std::any`] or a crate like [`downcast-rs`]. This crate offers two things, a trait [`DynCast`] that abstracts over methods used to cast between trait objects and some macros to minimize the boilerplate needed to implement that trait. ## Usage You should use the [`DynCast`] trait in trait bounds or as a supertrait and then do casts using the methods provided by the [`DynCastExt`] trait. The [`DynCast`] trait takes a type parameter that should be a "config" type generated by the [`create_dyn_cast_config`] macro, this type defines from which trait and to which trait a cast is made. Types that need to allow casting to meet the [`DynCast`] trait bound can then implement it via the [`impl_dyn_cast`] macro. ## Examples ```rust use cast_trait_object::{create_dyn_cast_config, impl_dyn_cast, DynCast, DynCastExt}; create_dyn_cast_config!(SuperToSubCast = Super => Sub); create_dyn_cast_config!(SuperUpcast = Super => Super); trait Super: DynCast + DynCast {} trait Sub: Super {} struct Foo; impl Super for Foo {} impl Sub for Foo {} impl_dyn_cast!(Foo as Super => Sub, Super); let foo: &dyn Super = &Foo; // Casting to a sub trait is fallible (the error allows us to keep using the // `dyn Super` trait object if we want which can be important if we are casting // movable types like `Box`): let foo: &dyn Sub = foo.dyn_cast().ok().unwrap(); // Upcasting to a supertrait is infallible: let foo /*: &dyn Super*/ = foo.dyn_upcast::(); ``` When implementing the [`DynCast`] trait via the [`impl_dyn_cast`] macro you can also list the created "config" types instead of the source and target traits: ```rust impl_dyn_cast!(Foo => SuperToSubCast, SuperUpcast); ``` If the `proc-macros` feature is enabled (which it is by default) we can also use procedural attribute macros to write a little bit less boilerplate: ```rust use cast_trait_object::{dyn_cast, dyn_upcast, DynCastExt}; #[dyn_cast(Sub)] #[dyn_upcast] trait Super {} trait Sub: Super {} struct Foo; #[dyn_cast(Sub)] #[dyn_upcast] impl Super for Foo {} impl Sub for Foo {} ``` Note that `#[dyn_upcast]` does the same as `#[dyn_cast(Super)]` but it is a bit clearer about intentions: ```rust use cast_trait_object::{dyn_cast, DynCastExt}; #[dyn_cast(Super, Sub)] trait Super {} trait Sub: Super {} struct Foo; #[dyn_cast(Super, Sub)] impl Super for Foo {} impl Sub for Foo {} let foo: &dyn Sub = &Foo; // Upcasting still works: let foo /*: &dyn Super*/ = foo.dyn_upcast::(); ``` ## Generics Generics traits and types are supported and both the declarative macros ([`impl_dyn_cast`], [`create_dyn_cast_config`], [`impl_dyn_cast_config`]) and the procedural attribute macros ([`dyn_cast`] and [`dyn_upcast`]) can be used with generics. ```rust use cast_trait_object::{DynCastExt, dyn_cast, dyn_upcast}; // Define a source and target trait: #[dyn_cast(Sub)] #[dyn_upcast] trait Super {} trait Sub: Super {} // Since `T` isn't used for `Color` it doesn't need to be `'static`: struct Color(u8, u8, u8); #[dyn_cast(Sub)] #[dyn_upcast] impl Super for Color {} impl Sub for Color {} struct Example(T); #[dyn_cast(Sub)] #[dyn_upcast] impl Super for Example {} impl Sub for Example {} let as_sub: &dyn Sub = &Example(false); let upcasted: &dyn Super = as_sub.dyn_upcast(); let _downcasted /*: &dyn Sub */ = upcasted.dyn_cast::>().ok().unwrap(); ``` Note that one limitation of the current support for generic types is that if the type that implements [`DynCast`] has any generic type parameters then they might need to be constrained to be `'static`. There is also another limitation with generic types and this one can be a bit counter intuitive. The [`DynCast`] implementations that are generated by the macros must always succeed or always fail. This means that if a target trait is only implemented for a subset of the types that the [`DynCast`] trait is implemented for then the cast will always fail. ```rust use cast_trait_object::{create_dyn_cast_config, impl_dyn_cast, DynCast, DynCastExt}; // Define a source and target trait: create_dyn_cast_config!(UpcastConfig = Super => Super); create_dyn_cast_config!(SuperConfig = Super => Sub); trait Super: DynCast + DynCast {} trait Sub: Super {} /// Only implements `Sub` for types that implement `Display`. struct OnlyDisplayGeneric(T); impl Super for OnlyDisplayGeneric {} impl Sub for OnlyDisplayGeneric {} // The cast to `Sub` will always fail since this impl of DynCast includes // some `T` that don't implement `Display`: impl_dyn_cast!(for OnlyDisplayGeneric as Super where {T: 'static} => Sub); impl_dyn_cast!(for OnlyDisplayGeneric as Super where {T: 'static} => Super); // &str does implement Display: let _is_display: &dyn core::fmt::Display = &""; // But the cast will still fail: let as_super: &dyn Super = &OnlyDisplayGeneric(""); assert!(as_super.dyn_cast::().is_err()); // `OnlyDisplayGeneric<&str>` does implement `Sub`: let as_sub: &dyn Sub = &OnlyDisplayGeneric(""); // Note that this means that we can perform an upcast and then fail to downcast: let upcasted: &dyn Super = as_sub.dyn_upcast(); assert!(upcasted.dyn_cast::().is_err()); ``` The best way to avoid this problem is to have the same trait bounds on both the source trait implementation and the target trait implementation. ## How it works ### How the conversion is preformed Using the [`DynCast`] trait as a supertraits adds a couple of extra methods to a trait object's vtable. These methods all essentially take a pointer to the type and returns a new fat pointer which points to the wanted vtable. There are a couple of methods since we need to generate one for each type of trait object, so one for each of `&dyn Trait`, `&mut dyn Trait`, `Box`, `Rc` and `Arc`. Note that these methods are entirely safe Rust code, this crate doesn't use or generate any unsafe code at all. These methods work something like: ```rust trait Super {} trait Sub { fn upcast(self: Box) -> Box; } impl Super for () {} impl Sub for () { fn upcast(self: Box) -> Box { self } } let a: Box = Box::new(()); let a: Box = a.upcast(); ``` The [`DynCastExt`] trait then abstracts over the different types of trait objects so that when a call is made using the [dyn_cast](DynCastExt::dyn_cast) method the compiler can inline that static method call to the correct method on the trait object. ### Why "config" types are needed We have to generate "config" types since we need to uniquely identify each [`DynCast`] supertrait based on which trait it is casting from and into. Originally this was just done using two type parameters on the trait, something like `DynCast`, but that caused compile errors when they were used as a supertrait of one of the mentioned traits. So now the traits are "hidden" as associated types on a generated "config" type. To make this "config" type more ergonomic we also implement a [`GetDynCastConfig`] trait to easily go from the source trait and target trait to a "config" type via something like `>::Config`. This allows the macros ([`impl_dyn_cast`], [`dyn_cast`] and [`dyn_upcast`]) to take traits as arguments instead of "config" types, it also makes type inference work for the [`DynCastExt`] trait. ### How the macros know if a type implements a "target" trait or not When a type implementing [`DynCast`] for a specific config and therefore source to target trait cast the generated code must choose if the cast is going to succeed or not. We want to return `Ok(value as &dyn Target)` if the type implements the `Target` trait and `Err(value as &dyn Source)` if it doesn't. We can use a clever hack to only preform the coercion if a type actually implements the target trait. See dtolnay's [Autoref-based stable specialization] (https://github.com/dtolnay/case-studies/tree/master/autoref-specialization) case study for more information about how this hack works. In short the hack allows us to call one method if a trait bound is met and another method if it isn't. In this way we can call a helper method that performs the coercion to the target trait only if the type actually implements that trait. So we could generate something like: ```rust trait Source {} trait Target {} struct Foo; impl Source for Foo {} struct Fallback; impl Fallback { fn cast<'a, T: Source>(&self, value: &'a T) -> &'a dyn Source { value } } struct HasTrait(core::marker::PhantomData); impl HasTrait { fn new() -> Self { Self(core::marker::PhantomData) } } impl HasTrait { fn cast<'a>(&self, value: &'a T) -> &'a dyn Target { value } } impl core::ops::Deref for HasTrait { type Target = Fallback; fn deref(&self) -> &Self::Target { static FALLBACK: Fallback = Fallback; &FALLBACK } } let used_fallback: &dyn Source = HasTrait::::new().cast(&Foo); ``` So the [`impl_dyn_cast`] macro works by generating a struct that implements [`core::ops::Deref`] into another type. Both types have a `cast` method but they do different things. The first struct's `cast` method has a trait bound so that it is only implemented if the cast can succeed. If the first method can't be used the compiler will insert a deref operation (`&*foo`) and see if there is a method that can apply after that. In this case that means that the `Fallback` structs method is called. This way the generated code doesn't call the method that preform the coercion to the `Target` trait unless the type actually implements it. ## Alternatives The [`intertrait`] crate offers similar functionality to this crate but has a totally different implementation, at least as of [`intertrait`] version `0.2.0`. It uses the [`linkme`] crate to create a registry of [`std::any::Any`] type ids for types that can be cast into a certain trait object. This means it probably has some runtime overhead when it looks up a cast function in the global registry using a [`TypeId`]. It also means that it can't work on all platforms since the [`linkme`] crate needs to offer support for them. This is a limitation that this crate doesn't have. The [`traitcast`] crate works similar to [`intertrait`] in that it has a global registry that is keyed with [`TypeId`]s. But it differs in that it uses the [`inventory`] crate to build the registry instead of the [`linkme`] crate. The [`inventory`] crate uses the [`ctor`] crate to run some code before `main`, something that is generally discouraged and this is something that [`intertrait`] actually mentions as an advantage to its approach. The [`traitcast_core`] library allow for a more low level API that doesn't depend on a global registry and therefore also doesn't depend on a crate like [`linkme`] or [`inventory`] that needs platform specific support. Instead it requires that you explicitly create a registry and register all your types and their casts with it. The [`downcast-rs`] crate offers downcasting to concrete types but not directly casting from one trait object to another trait object. So it has a different use case and both it and this crate could be useful in the same project. You could just define methods on your traits similar to the ones provided by this crate's [`DynCast`] trait. Doing this yourself can be more flexible and you could for example minimize bloat by only implementing methods for casts that you actually require. The disadvantage is that it would be much less ergonomic than what this crate offers. ## References The following GutHub issue [Clean up pseudo-downcasting from VpnProvider supertrait to subtraits with better solution · Issue #21 · jamesmcm/vopono](https://github.com/jamesmcm/vopono/issues/21) inspired this library. This library was mentioned in the following blog post in the "Upcasting" section: [So you want to write object oriented Rust :: Darrien's Blog — Dev work and musings](https://blog.darrien.dev/posts/so-you-want-to-object/#upcasting) ## License This project is released under either: - [MIT License](https://github.com/Lej77/cast_trait_object/blob/master/LICENSE-MIT) - [Apache License (Version 2.0)](https://github.com/Lej77/cast_trait_object/blob/master/LICENSE-APACHE) at your choosing. [`std::any`]: https://doc.rust-lang.org/std/any [`std::any::Any`]: https://doc.rust-lang.org/std/any/trait.Any.html [`TypeId`]: https://doc.rust-lang.org/std/any/struct.TypeId.html [`downcast-rs`]: https://crates.io/crates/downcast-rs [`intertrait`]: https://crates.io/crates/intertrait [`traitcast`]: https://crates.io/crates/traitcast [`traitcast_core`]: https://crates.io/crates/traitcast_core [`linkme`]: https://crates.io/crates/linkme [`inventory`]: https://crates.io/crates/inventory [`ctor`]: https://crates.io/crates/ctor ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.