https://github.com/NyxCode/proxy-enum
Emulate dynamic dispatch and sealed classes using a proxy enum, which defers all method calls to its variants.
https://github.com/NyxCode/proxy-enum
Last synced: 11 months ago
JSON representation
Emulate dynamic dispatch and sealed classes using a proxy enum, which defers all method calls to its variants.
- Host: GitHub
- URL: https://github.com/NyxCode/proxy-enum
- Owner: NyxCode
- Created: 2020-04-28T21:24:43.000Z (almost 6 years ago)
- Default Branch: master
- Last Pushed: 2021-06-06T05:48:14.000Z (almost 5 years ago)
- Last Synced: 2025-04-21T09:53:41.872Z (11 months ago)
- Language: Rust
- Homepage:
- Size: 11.7 KB
- Stars: 19
- Watchers: 3
- Forks: 2
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# proxy-enum
[](https://crates.io/crates/proxy-enum)
[](https://docs.rs/proxy-enum)
Emulate dynamic dispatch and ["sealed classes"](https://kotlinlang.org/docs/reference/sealed-classes.html) 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.
```rust
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:
```rust
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
```rust
#[proxy_enum::proxy(Animal)]
mod proxy {
enum Animal {
Cat(Cat),
Lion(Lion),
Mouse(Mouse)
}
impl Animal {
#[implement]
fn feed(&self) {}
}
}
```
This will expand to:
```rust
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 for Animal { /* ... */ }
impl From for Animal { /* ... */ }
impl From for Animal { /* ... */ }
}
```
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:
```rust
#[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:
```rust
#[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 {}
}
```