I’m looking for feedback on a pattern for doing type-based dispatch from a fieldless enum.
The situation is roughly this:
#[derive(Clone, Copy)]
enum MyEnum {
VarA,
VarB,
}
struct VarA;
struct VarB;
trait MyVarTrait {
fn do_trait() {}
}
impl MyVarTrait for VarA {}
impl MyVarTrait for VarB {}
trait Executable {
fn execute<T: MyVarTrait>(self);
}
impl MyEnum {
fn do_a_plus_b_twice(self, a: i32, b: i32) {
struct Op(i32, i32);
impl Executable for Op {
fn execute<T: MyVarTrait>(self) {
let Op(a, b) = self;
T::do_trait();
T::do_trait();
println!("{} + {}", a, b);
}
}
self.visit(Op(a, b))
}
fn do_a_minus_b_twice(self, a: i32, b: i32) {
struct Op(i32, i32);
impl Executable for Op {
fn execute<T: MyVarTrait>(self) {
let Op(a, b) = self;
T::do_trait();
T::do_trait();
println!("{} - {}", a, b);
}
}
self.visit(Op(a, b))
}
fn visit<E: Executable>(self, executable: E) {
match self {
Self::VarA => executable.execute::<VarA>(),
Self::VarB => executable.execute::<VarB>(),
}
}
}
The idea is that MyEnum is a fieldless enum used as a selector, while each variant corresponds to a concrete type (VarA, VarB).
I want the enum-to-type mapping to live in one place:
match self {
Self::VarA => executable.execute::<VarA>(),
Self::VarB => executable.execute::<VarB>(),
}
Then individual operations can define a local Op type which carries whatever context that operation needs.
This gives me static dispatch and avoids repeating the same match in every operation.
If the enum variants carried the actual implementation values, e.g.
enum MyEnum {
VarA(VarA),
VarB(VarB),
}
then something like enum_dispatch would fit naturally.
But in my real use case I specifically want a fieldless enum, because it is also used as a list of available variants, for example with strum for enumeration/UI selection.
So I effectively want:
fieldless enum variant
-> concrete Rust type
-> generic operation over that type
The pattern above works, but the local struct Op + impl Executable is somewhat verbose. What I would ideally like is something analogous to a generic closure, conceptually:
self.visit(|T| {
T::do_trait();
// captured local context...
});
but Rust closures cannot have a generic call<T>(), so I ended up using the local Op type instead.
Is there a more idiomatic way to model this?
In particular, I’d be interested in approaches that:
- keep the enum fieldless,
- preserve static dispatch / monomorphization,
- keep the enum-to-concrete-type mapping in one place,
- allow operations to carry local context without repeating a large
match, - and ideally avoid excessive boilerplate.
Would you keep this visitor-like pattern, use a macro, restructure the types, or approach it differently?