disponent 0.2.1

An alternative to dynamic-dispatched method calls, without the need for object safety
Documentation

disponent

disponent is an alternative to using dyn Trait trait objects for dispatching to multiple implementations of a trait. Works with async methods, generics, #[cfg] attributes, no_std and even traits that are not object safe.

Usage

use disponent::declare;

declare!(
    pub enum FooOrBar {
        Foo(Foo),
        Bar(Bar),
    }

    pub trait SayHello {
        fn say_hello(&self) -> impl Future<Output = ()>;
        fn name(&self) -> &'static str;

        // Default implementations work too
        fn with_default(&self) -> &'static str {
            "default"
        }
    }
);

pub struct Foo;
impl SayHello for Foo {
    async fn say_hello(&self) {
        println!("Hello from Foo")
    }
    fn name(&self) -> &'static str { "Foo" }
}

pub struct Bar;
impl SayHello for Bar {
    async fn say_hello(&self) {
        println!("Hello from Bar")
    }
    fn name(&self) -> &'static str { "Bar" }
}

fn main() {
    // `FooOrBar` implements `SayHello` by delegating to the inner type
    let foo_or_bar = FooOrBar::Foo(Foo);
    smol::block_on(foo_or_bar.say_hello());
    println!("My name is {}", foo_or_bar.name());
}

Configuration Options

Apply #[disponent::configure(...)] to the enum with any combination of:

  • inherent: Generate inherent methods on the enum (vs trait impl)
  • inline: Add #[inline] to all generated methods
  • from: Generate From impls for each variant
  • try_into: Generate TryInto impls for each variant

Remote Traits

Use #[disponent::remote(path::to::Trait)] on the trait to forward to a trait defined elsewhere:

use external_crate::ExternalTrait;

declare!(
    pub enum MyEnum {
        Foo(Foo),
        Bar(Bar),
    }

    #[disponent::remote(ExternalTrait)]
    trait LocalTraitMirror {
        fn method(&self);
    }
);

Generated Code

The above example generates the following code:

#[automatically_derived]
impl SayHello for FooOrBar {
    async fn say_hello(&self) -> () {
        match self {
            FooOrBar::Foo(inner) => SayHello::say_hello(inner).await,
            FooOrBar::Bar(inner) => SayHello::say_hello(inner).await,
        }
    }
    fn name(&self) -> &'static str {
        match self {
            FooOrBar::Foo(inner) => SayHello::name(inner),
            FooOrBar::Bar(inner) => SayHello::name(inner),
        }
    }
    fn with_default(&self) -> &'static str {
        match self {
            FooOrBar::Foo(inner) => SayHello::with_default(inner),
            FooOrBar::Bar(inner) => SayHello::with_default(inner),
        }
    }
}

In many cases, this can be substantially faster than using dyn Trait trait objects, especially when the enum is small and the methods are simple. See the benchmarks of enum_dispatch for more details (benchmarks for disponent are coming soon).

See also

With the exception of declarative_enum_dispatch, these crates all share state between macro invocations, which can lead to issues with rust-analyzer and similar tools. disponent avoids this by generating all code in a single macro invocation, at the cost of some flexibility in how the traits are defined (all while rust-fmt and error reporting works like normal).

License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in disponent by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.