Skip to content

Get your own Any with support for casting to trait objects.

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT
Notifications You must be signed in to change notification settings

ink-feather-org/trait_cast_rs

Repository files navigation

trait_cast_rs

Daily-Nightly Rust-Main-CI docs.rs crates.io rustc

Requirements

This crate requires a nightly compiler.

What can this crate do?

This crate adds the TraitcastableAny replacement trait for Any. It closely resembles the Any trait for downcasting to a concrete type. Additionally the TraitcastableAny trait allows you to directly downcast to other &dyn Traits.

To make this work you must specify all target traits you want to be able to downcast to in the make_trait_castable(Trait1, Trait2, ...) attribute macro. This macro can be applied to structs, enums and unions. It implements the TraitcastableAny trait for your struct, enum or union.

Note: No modifications on the target traits are necessary. Which allows you to downcast to traits of other libraries you don't control.

Usage

  1. Add the trait_cast_rs crate to your Cargo.toml and switch to a nightly compiler.

  2. Add the #[make_trait_castable(Trait1, Trait2, ...)] macro to your struct/enum/union. List all traits you eventually want to be able to downcast to. You must implement all listed traits.

  3. Use references to dyn TraitcastableAny throughout your code instead of dyn Any.

  4. Enjoy downcasting to trait objects.

Example

use trait_cast_rs::{
  make_trait_castable, TraitcastableAny, TraitcastableAnyInfra, TraitcastableAnyInfraExt,
};
#[make_trait_castable(Print)]
struct Source(i32);
trait Print {
  fn print(&self);
}
impl Print for Source {
  fn print(&self) {
    println!("{}", self.0)
  }
}

let source = Box::new(Source(5));
let castable: Box<dyn TraitcastableAny> = source;
let x: &dyn Print = castable.downcast_ref().unwrap();
x.print();

EVEN MORE Examples 🔥

Check out the examples.

If you want to do something the make_trait_castable attribute macro can't handle (like implementing for generic structs - pull requests are welcome) check out the manual*.rs examples.

There is also a decl marco available - check out the with_decl_macro*.rs examples.

Features

  • alloc - Adds special implementations for Box, Rc and Arc. Default feature.

  • const_sort - Makes the make_trait_castable and make_trait_castable_decl macros sort the traitcast_targets at compile_time. When downcasting a binary_search is performed. May be 🚀 BLAZINGLY 🚀 faster for types with lots of downcast targets.

    It additionally requires the following feature flags in the user code: #![feature(const_trait_impl, const_mut_refs)]

  • min_specialization - Implements TraitcastableAny for 'static types. Even types you don't control. However these default implementations of TraitcastableAny have no downcast targets.

    It additionally requires the following feature flags in the user code: #![feature(min_specialization)]

  • downcast_unchecked - Adds *_unchecked variants to the downcast functions.

Upcasting to the real Any

With the trait_upcasting rust feature you can even cast any &dyn TraitcastableAny to &dyn Any. Alternatively you can list the Any trait as a traitcast target. However it is not possible to cast back to TraitcastableAny (pull requests are welcome).

Authors

raldone01 and onestacked are the primary authors and maintainers of this library.

License

This project is released under either:

at your choosing.

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.

How it works

I will give you a quick rundown of our internal operations: 💦

Compile time:

  1. Add a casting function for every downcast path to the concrete type. This function gets a dyn TraitcastableAny, which it then downcasts to a concrete type using Any in the background. In the last step it casts the concrete type to the wanted trait object and returns it.

  2. Add a traitcast_targets function that returns a const slice of (typeid, transmuted casting function ptr).

Runtime:

  1. Get targets array
  2. Find the target typeid
  3. Transmute function pointer back to original type
  4. Call the function pointer to get the wanted trait object
  5. Return it
  6. 💲 Profit 💲

SAFETY 🏰

  • The unchecked variants of the downcast function all use unsafe - expectedly.
  • The only other use of unsafe is the transmutation of function pointers. However when they are called they are transmuted back to their original type. So this should be 105% save. As long as TypeIds don't collide.

Alternatives (and why our crate is the best)

This alternatives section is not exhaustive for a more objective/detailed comparison see the alternatives section of cast_trait_object.

  • mopa: Had its last update 6 years ago. Has some unresolved unsoundness issues. Also requires modifications on traits themselves while we just modify the struct/enum/union (see note above).
  • mopa-maintained: Might have fixed some issues but still has an old code base with just a version bump.
  • traitcast: Has no readme on crates.io. Uses a GLOBAL REGISTRY with lazy_static. To be fair it allows you to use the default Any and doesn't require nightly.

TODO: Remove this section once our last update is 6 years old.

Links

std::any

std::any::Any

TypeId

downcast-rs

intertrait

traitcast

traitcast_core

cast_trait_object

mopa

mopa-maintained