Crate trait_map

Source
Expand description

§Trait Map

trait_map provides the TraitMap data structure, which can store variable data types and expose traits on those types. Types must implement the TraitMapEntry trait, which provides on_create() and on_update() hooks for specifying which traits should be exposed in the map.

Warning: This crate must be compiled using Rust Nightly. It uses the ptr_metadata and unsize features for working with raw pointers.

§Usage

Assume we have some custom structs and traits defined:

trait ExampleTrait {
  fn do_something(&self) -> u32;
  fn do_another_thing(&mut self);
}

trait ExampleTraitTwo {
  fn test_method(&self);
}

struct MyStruct {
  // ...
}

struct AnotherStruct {
  // ...
}

impl ExampleTrait for MyStruct {
  fn do_something(&self) -> u32 { /* Code */ }
  fn do_another_thing(&mut self) { /* Code */ }
}

impl ExampleTrait for AnotherStruct {
  fn do_something(&self) -> u32 { /* Code */ }
  fn do_another_thing(&mut self) { /* Code */ }
}

impl ExampleTraitTwo for AnotherStruct{
  fn test_method(&self) { /* Code */ }
}

We can specify that we want to allow our struct types to work with the trait map by implementing the TraitMapEntry trait:

use trait_map::{TraitMapEntry, Context};

impl TraitMapEntry for MyStruct {
  fn on_create<'a>(&mut self, context: Context<'a>) {
    // Must explicitly list which traits to expose
    context
      .downcast::<Self>()
      .add_trait::<dyn ExampleTrait>();
  }

  // Can be overridden to update the exposed traits in the map
  fn on_update<'a>(&mut self, context: Context<'a>) {
    context
      .downcast::<Self>()
      .remove_trait::<dyn ExampleTrait>();
  }
}

impl TraitMapEntry for AnotherStruct {
  fn on_create<'a>(&mut self, context: Context<'a>) {
    context
      .downcast::<Self>()
      .add_trait::<dyn ExampleTrait>()
      .add_trait::<dyn ExampleTraitTwo>();
  }
}

Once this is done, we can store instances of these concrete types inside TraitMap and query them by trait. For example:

use trait_map::TraitMap;

fn main() {
  let mut map = TraitMap::new();
  map.add_entry(MyStruct { /* ... */ });
  map.add_entry(AnotherStruct { /* ... */ });

  // Can iterate over all types that implement ExampleTrait
  //  Notice that entry is "&dyn mut ExampleTrait"
  for (entry_id, entry) in map.get_entries_mut::<dyn ExampleTrait>() {
    entry.do_another_thing();
  }

  // Can iterate over all types that implement ExampleTraitTwo
  //  Notice that entry is "&dyn ExampleTraitTwo"
  for (entry_id, entry) in map.get_entries::<dyn ExampleTraitTwo>() {
    entry.test_method();
  }
}

§Deriving

If you enable the derive feature flag, then you automatically implement the TraitMapEntry trait. You must specify which traits to expose to the map using one or more #[trait_map(...)] attributes. When compiling on nightly, it uses the proc_macro_diagnostic feature to emit helpful compiler warnings.

As a small optimization, duplicate traits will automatically be removed when generating the trait implementation (even though calls to .add_trait() are idempotent). However, macros cannot distinguish between types aliased by path, so doing something like #[trait_map(MyTrait, some::path::MyTrait)] will generate code to add the trait twice even though MyTrait is the same trait.

use trait_map::TraitMapEntry;

// ...

#[derive(Debug, TraitMapEntry)]
#[trait_map(ExampleTrait, ExampleTraitTwo)]
#[trait_map(std::fmt::Debug)]
struct DerivedStruct {
  // ...
}

impl ExampleTrait for DerivedStruct {
  fn do_something(&self) -> u32 { /* Code */ }
  fn do_another_thing(&mut self) { /* Code */ }
}

impl ExampleTraitTwo for DerivedStruct{
  fn test_method(&self) { /* Code */ }
}

Structs§

Context
Stores type information about an entry inside of a TraitMap.
EntryID
Opaque ID type for each entry in the trait map.
TraitMap
Map structure that allows types to be dynamically queries by trait.
TypedContext
Stores concrete type for an entry inside a TraitMap

Traits§

TraitMapEntry
Rust type that can be stored inside of a TraitMap.