#traits #meta-programming #registry #macro

traitreg

Create a registry of implementations of a trait

3 releases (breaking)

0.3.0 Sep 6, 2024
0.2.0 Sep 5, 2024
0.1.0 Sep 5, 2024

#587 in Rust patterns

Download history 352/week @ 2024-09-04 30/week @ 2024-09-11 25/week @ 2024-09-18 30/week @ 2024-09-25 16/week @ 2024-10-02

78 downloads per month

MIT/Apache

15KB
123 lines

Traitreg

Create a registry of implementations of a trait. Useful for all kinds of metaprogramming, but in particular can be used for:

  • Dependency injection (at runtime)
  • Registration for plugins or middleware
  • Any code which needs to do something for a number of types

API

Register a trait implementation

trait MyTrait {}
struct MyType;

#[traitreg::register]
impl MyTrait for MyType {}

Optionally: register with a constructor

trait MyTrait {}

#[derive(Default)]
struct MyType;

#[traitreg::register(default)]
impl MyTrait for MyType {}

struct MyOtherType;
impl MyOtherType {
    fn new() -> Self { Self }
}

#[traitreg::register(new)]
impl MyTrait for MyOtherType {}

Build a static registry of all registered trait implementations.

#[traitreg::registry(MyTrait)]
static MYTRAIT_REGISTRY: () = ();

Access registry contents.

// Enumerate impls
for reg in MYTRAIT_REGISTRY.iter() {
    println!("{reg:#?}");

    // Instanciate 
    let instance: Option<Box<dyn MyTrait>> = reg.instanciate();
}

Implementation Details

The registry is built during startup by methods called by the linker, before main() is called. This approach is very much platform dependent but avoids issues with other approaches which run at compile-time but are unsound.

Notably multiple crates (i.e. compilation units) can register implementations independently, the registry will pick up all of the impls automatically at runtime. This can be useful for a plugin system where shared libraries (cdylib crates) are loaded. Currently loading shared libraries manually after main() is called will not update the registry.

It is possible to build a registry like this purely at compile time using procedural macros but as far as I am aware this is unsound. Each proc macro invocation currently reuses the same proc-macro executable in-memory without reloading it, so state can be persisted in static memory, but storing state across several independent macro calls is not supported by rustc and this behaviour may break in the future.

Outstanding Issues

  • Initialization order is not guaranteed on apple platforms, registered types may be missing from the registry.
  • Registered implementations for traits with the same name will conflict, even if those traits are in seperate modules or crates.

Similar / Previous Work

Dependencies

~215–650KB
~16K SLoC