Crates.io | enum_delegate |
lib.rs | enum_delegate |
version | 0.2.0 |
source | src |
created_at | 2022-11-02 20:25:28.033201 |
updated_at | 2022-11-05 14:23:08.4944 |
description | Easily replace dynamic dispatch with an enum, for speed and serialization |
homepage | |
repository | https://gitlab.com/dawn_app/enum_delegate |
max_upload_size | |
id | 703721 |
size | 46,210 |
Easily replace dynamic dispatch with an enum, for speed and serialization.
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.
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.
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.
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.
enum_delegate
can also handle traits with associated types.
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.
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.
Some edge cases, such as generic traits or supertraits, are currently not supported.
enum_dispatch
, which this crate was inspired from, solves the same problem. However, as discussed below, enum_delegate
has a few extra features.Box<dyn YourTrait>
. However, this will be slower (see benchmarks
) 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, using enum_derive::EnumInnerAsTrait
. This is slower though, and I am not aware of any advantages of doing this over enum_delegate
.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.)