Hi,
The title is basically kind of an XY Problem, but I found both X and Y are interesting to discuss about so I took Y as the title, please do not blame me.
My original question came from the use of enum_dispatch. Its example code is:
struct MyImplementorA;
struct MyImplementorB;
impl MyBehavior for MyImplementorA { /*...*/ }
impl MyBehavior for MyImplementorB { /*...*/ }
#[enum_dispatch]
enum MyBehaviorEnum {
MyImplementorA,
MyImplementorB,
}
#[enum_dispatch(MyBehaviorEnum)]
trait MyBehavior {
fn my_trait_method(&self);
}
let a: MyBehaviorEnum = MyImplementorA::new().into();
a.my_trait_method(); // no dynamic dispatch
I found the definition of MyBehaviorEnum
to be a bit cumbersome. Every time we create a new struct that implements MyBehavior
, we must manually add it to the enum definition. If we decide to rename or merge similar structs while refactoring, it will require double changes which complicates project maintenance. Additionally, as more implementers are added, the enum definition becomes lengthier too.
So what if we could write the structs only once, and let the library generate the rest (i.e. the enums)? I imagine something simpler like:
#[enum_dispatch(MyBehaviorEnum)] // send MyImplementorA to MyBehaviorEnum
struct MyImplementorA;
#[enum_dispatch(MyBehaviorEnum)] // send MyImplementorB to MyBehaviorEnum
struct MyImplementorB;
#[enum_dispatch]
enum MyBehaviorEnum {
/* auto generated */
}
#[enum_dispatch(MyBehaviorEnum)]
trait MyBehavior {
fn my_trait_method(&self);
}
Now I am wondering about how feasible it is. Rust seems not to provide procedure macros with a way of gathering global states, but a crate called macro_state
manages to accomplish it by writing temporary files during macro running.
But it looks like there are still some gaps to implement these with enum_dispatch
. I have heard that Rust macros are "hygienic" so (maybe) it is hard to interact with macros in another crate.
In fact I had some initial attempts by modifying macro_state
to add a macro called #[auto_enum("key")]
, which collects all values from append_auto_enum!(key, value)
to create an enum:
mod A { append_auto_enum!("foo", MyImplementorA); }
mod B { append_auto_enum!("foo", MyImplementorB); }
#[enum_dispatch]
#[auto_enum("foo")] // --> will expand to `enum MyBehaviorEnum { MyImplementorA, MyImplementorB }`
enum MyBehaviorEnum {}
// MyBehaviorEnum::MyImplementorA is available here!
However, enum_dispatch
complains that it does not find any variant of MyBehaviorEnum
, thus generate non-exhaustive match patterns:
error[E0004]: non-exhaustive patterns: type `&MyBehaviorEnum` is non-empty
--> src/main.rs:96:5
|
96 | #[enum_dispatch]
| ^^^^^^^^^^^^^^^^
|
note: `MyBehaviorEnum` defined here
--> src/main.rs:98:14
|
98 | pub enum MyBehaviorEnum{}
| ^^^^^^^^^^^^^^^^^
= note: the matched value is of type `&MyBehaviorEnum`
= note: this error originates in the attribute macro `enum_dispatch` (in Nightly builds, run with -Z macro-backtrace for more info)
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern as shown
|
96 ~ #[enum_dispatch] {
97 + _ => todo!(),
98 + }
|
So maybe there are some better methods to achieve it? Maybe I have to modify enum_dispatch
itself? Maybe "having something auto-generated from different part of codes" is just a bad idea? ... Any suggestion or discussion is welcomed.
Disclaimer: I am not a native English speaker. Please correct me if there is anything mistaken or confusing in my post.