2 unstable releases
0.2.0 | Nov 5, 2022 |
---|---|
0.1.0 | Nov 2, 2022 |
#23 in #dynamic-dispatch
6,739 downloads per month
Used in 12 crates
(3 directly)
55KB
1K
SLoC
Easily replace dynamic dispatch with an enum, for speed and serialization.
What it Does
In Rust, you can use something like Box<dyn YourTrait>
to hold any possible implementation of your trait. But this is a bit slow, and doesn't work well with serialization. The solution is to put all the implementations you need in an enum. This library can automatically implement the trait for the enum, as well as a bunch of useful conversions. Then, you can use your enum instead of using dynamic dispatch.
tldr: like the amazing enum_dispatch
, but more amazing.
Walk-through
Add enum_delegate
to each crate you plan to use it in:
enum_delegate = "0.1"
Annotate your trait with #[enum_delegate::register]
:
#[enum_delegate::register]
trait SayHello {
fn say_hello(&self, name: &str) -> String;
}
Create an enum with variants that all look like VariantName(VariantType)
. Each variant field must implement your trait. Now, annotate the enum with #[enum_delegate::implement(trait name)]
.
struct Arthur;
impl SayHello for Arthur {...}
struct Pablo;
impl SayHello for Pablo {...}
#[enum_delegate::implement(SayHello)]
enum People {
Arthur(Arthur),
Pablo(Pablo),
}
Your trait will be implemented for the enum, and conversions such as From<Arthur>
and TryInto<Arthur>
will also be generated. You can now use it similarly to a Box<dyn SayHello>
, but it will be faster!
You can see this in action in the e1_simple.rs
example.
3rd-party Traits
What if the trait comes from a 3rd-party crate, and you can't add enum_delegate::register
to it? That's ok! Just pass the trait definition as the 2nd argument to enum_delegate::implement
:
#[enum_delegate::implement(SayHello,
trait SayHello {
fn say_hello(&self, name: &str) -> String;
}
)]
enum People {
Arthur(Arthur),
Pablo(Pablo),
}
Like before, your trait will be implemented for the enum. Conversions such as From<Arthur>
and TryInto<Arthur>
will also be generated.
3rd-party Enums
What if the enum comes from a 3rd-party crate, and you can't add enum_delegate::implement
to it? That's also fine! Use the enum_delegate::implement_for
attribute, and pass the trait name & trait definition to it:
#[enum_delegate::implement_for(People,
enum People {
Arthur(Arthur),
Pablo(Pablo),
}
)]
trait SayHello {
fn say_hello(&self, name: &str) -> String;
}
This will implement the trait for the specified enum.
Note: since the enum comes from another crate in this case,
enum_delegate
can't and won't implement theFrom
andTryInto
traits for it.
Associated Types
enum_delegate
can also handle traits with associated types.
Same Type
By default, enum_delegate
requires all the involved implementers to provide the same value for the same associated type. If you specify different types, it won't compile.
Check the e2_associated_type.rs
example for how to use simple associated types.
Mixed Types
If you need to mix different types in the same enum, you need to annotate your enum with #[enum_delegate(unify = "enum_wrap")]
. This will generate a new enum wrapping the possible types for the associated type. From
and TryInto
conversions will be automatically generated to facilitate conversion between the enum and the different wrapper types.
This "secretly" generated enum's name is not a stable part of the public API. Use YourEnum::YourAssociatedType
to refer to this type, and From conversions to construct it.
Check the e3_mixed_associated_type.rs
example to see mixed associated types.
Limitations
Some edge cases, such as generic traits or supertraits, are currently not supported.
Alternatives
enum_dispatch
, which this crate was inspired from, solves the same problem. However, as discussed below,enum_delegate
has a few extra features.- Dynamic dispatch: instead of wrapping your types in an enum, you can use e.g. a
Box<dyn YourTrait>
. However, this will be slower (seebenchmarks
) and cannot be serialized by default.typetag
can help with serialization though. enum_derive
: derive a method to return a borrowed pointer to the inner value, cast to a trait object, usingenum_derive::EnumInnerAsTrait
. This is slower though, and I am not aware of any advantages of doing this overenum_delegate
.- Manually delegating the trait and other enum tricks: tedious, but might offer more flexibility in rare cases
Comparison with enum_dispatch
🟡 Performance: the same. This is expected, since they generate very similar code. (See benchmarks
in the repo.)
✅ Works across crates. Due to technical limitations of how enum_dispatch
is implemented, it can only be used if both the trait and enum are in the same crate. enum_delegate
, however, allows you to put them in separate crates. (See cross_crate_example
in the repo.)
✅ Better errors. Again due to technical limitations, in some cases enum_dispatch
will quietly fail. With enum_delegate
, your code will either succeed, or fail to compile. Admittedly, some of the error messages are not perfect, but at least you'll know something's up. (See tests_error
in the repo.)
✅ Associated types. enum_delegate
has some support for associated types, but enum_dispatch
doesn't. (See examples
in the repo.)
Dependencies
~2MB
~42K SLoC