[−][src]Crate proxy_enum
Emulate dynamic dispatch and "sealed classes" using a proxy enum, which defers all method calls to its variants.
Introduction
In rust, dynamic dispatch is done using trait objects (dyn Trait
).
They enable us to have runtime polymorphism, a way of expressing that a type implements a
certain trait while ignoring its concrete implementation.
let animal: &dyn Animal = random_animal(); animal.feed(); // may print "mew", "growl" or "squeak"
Trait objects come with a downside though: getting a concrete implementation back from a trait object (downcasting) is painfull. (see std::any::Any)
If you know there are only a finite number of implentations to work with, an enum
might be
better at expressing such a relationship:
enum Animal { Cat(Cat), Lion(Lion), Mouse(Mouse) } match random_animal() { Animal::Cat(cat) => cat.feed(), Animal::Lion(lion) => lion.feed(), Animal::Mouse(mouse) => mouse.feed() }
Some languages have special support for such types, like Kotlin with so called "sealed classes".
Rust, however, does not.
proxy-enum
simplifies working with such types using procedural macros.
Usage
#[proxy_enum::proxy(Animal)] mod proxy { enum Animal { Cat(Cat), Lion(Lion), Mouse(Mouse) } impl Animal { #[implement] fn feed(&self) {} } }
This will expand to:
mod proxy { enum Animal { Cat(Cat), Lion(Lion), Mouse(Mouse) } impl Animal { fn feed(&self) { match self { Animal::Cat(cat) => cat.feed(), Animal::Lion(lion) => lion.feed(), Animal::Mouse(mouse) => mouse.feed() } } } impl From<Cat> for Animal { fn from(from: Cat) -> Self { Animal::Cat(from) } } impl From<Lion> for Animal { fn from(from: Lion) -> Self { Animal::Lion(from) } } impl From<Mouse> for Animal { fn from(from: Mouse) -> Self { Animal::Mouse(from) } } }
This, however, will only compile if Cat
, Lion
and Mouse
all have a method called feed
.
Since rust has traits to express common functionality, trait implentations can be generated too:
#[proxy_enum::proxy(Animal)] mod proxy { enum Animal { Cat(Cat), Lion(Lion), Mouse(Mouse) } trait Eat { fn feed(&self); } #[implement] impl Eat for Animal {} }
Since the macro has to know which methods the trait contains, it has to be defined within the module. However, implementations for external traits can be generated too:
#[proxy_enum::proxy(Animal)] mod proxy { enum Animal { Cat(Cat), Lion(Lion), Mouse(Mouse) } #[external(std::string::ToString)] trait ToString { fn to_string(&self) -> String; } #[implement] impl std::string::ToString for Animal {} }
Attribute Macros
proxy |