whichever
Implements a trait for an enum by forwarding to whichever variant it holds.
// In the crate that owns the trait.
// In any crate that owns an enum over implementors.
AnyShape now implements Shape with one match per method, each arm a
fully qualified call into the variant's payload, and gets From<Circle> and
From<Square>:
That is the code a careful hand would write: no trait object, no closure, no conversion, and a variant's override of a default method is never hidden by the default.
Why another one
An enum over the implementors of a trait is the cheapest way to hold "one of these": no allocation, no vtable, and the compiler sees every arm. The forwarding impl is the price, paid again for every method and every variant.
enum_dispatch made it automatic, but it keeps the trait's definition in a
process-global cache inside the proc-macro server. That is why it cannot see
a trait from another crate (the enum compiles with no impl and the first
call fails), panics on associated types and receiver-less methods, and
loses the spans of its errors. whichever keeps nothing between
expansions: #[dispatchable] exports a hidden macro carrying the trait's
tokens, re-exported under the trait's own name, and #[dispatch] calls it.
The trait and the enum may live in different crates, in any order, and the
shapes real traits have (associated types, &Self arguments, generic
traits, several traits on one enum) dispatch too.
| whichever | enum_dispatch 0.3 | ambassador 0.5 | portrait 0.3 | enum_delegate 0.2 | delegation 0.5 | static-dispatch 0.3 | delegate 0.13 | |
|---|---|---|---|---|---|---|---|---|
| Trait and enum in different crates | yes | no, silently | yes | yes | yes | yes | yes | yes, signatures re-listed |
| Default methods forwarded | yes | yes | yes | yes | yes | yes | yes | if listed |
Self, &Self, &mut Self arguments |
yes, with a declared fallback | no | no | no | panics at runtime | no | no | no |
-> Self |
yes | yes | no | no | yes | no | no | with #[into] |
-> Option<Self>, -> Result<Self, E> |
yes | no | - | - | - | - | - | - |
| Associated types | yes | no | yes | no | yes | no | no | by hand |
| Differing associated types unified | yes, through Into |
no | no | no | yes, wrapper enum | no | no | by hand |
| Associated consts | yes | no | no | no | no | no | no | by hand |
| Methods without a receiver | with a default body | no | no | no | no | no | no | - |
async fn |
yes | yes | yes | no | no | no | yes | yes |
| Generic trait at a concrete instantiation | yes | no | yes | no | no | yes | no | by hand |
| Const generics on the trait | yes | - | - | - | - | - | - | - |
| Generic methods | yes | yes | yes | yes | no | yes | yes | yes |
| Lifetime-generic trait and enum | yes | no | yes | no | no | yes | no | by hand |
| Several traits on one enum | yes | yes | yes | yes | no | no | yes | yes |
| Payload with a same-named inherent method | safe | safe | safe | safe | safe | safe | safe | calls it, unless #[through] |
Box<T>, Rc<T>, &T payloads |
yes, #[dispatch(deref)] |
no | no | no | no | no | no | yes |
-> impl Trait |
no | no | no | no | no | no | no | no |
From<Payload> impls |
yes, opt-out; TryFrom<Enum> opt-in |
yes, plus a forced TryInto |
no | no | yes | no | no | no |
| Inherent methods forwarded to some variants, with a fallback | yes | no | no | no | no | no | no | by hand |
Probed on 2026-11-01 against the versions shown, with the trait in one
crate and the enum in another, on stable Rust. A dash means the case was
not probed for that crate. The whichever column is what tests/ checks.
Options
All options follow the trait path in #[dispatch(..)].
-
Name = Typefixes the associated typeNameinstead of taking it from the first variant. A method returningSelf::Nameis converted withInto, one returningResult<_, Self::Name>withmap_err(Into::into), so variants with their own error types can share one enum: -
mismatch(method = expr, ..)gives the result ofmethodwhen itsSelfarguments hold a different variant thanself. The trait author can decide once instead, with#[dispatch(mismatch = expr)]on the method: -
from = falsesuppresses theFrom<Payload>impls. -
try_from = trueaddsTryFrom<Enum> for Payloadfor every payload type, with the enum as the error, so a mismatch hands the value back.
One option goes on a variant. #[dispatch(deref)] calls the trait on what
the payload dereferences to, so Box<T>, Rc<T>, Arc<T>, &T and
&mut T payloads dispatch like a plain T:
Several traits are dispatched by repeating the attribute; a supertrait is one more attribute:
A generic trait is dispatched at a concrete instantiation:
#[whichever::dispatch(Encoder<Json>)]. Type, lifetime and const parameters
are substituted in every signature, and F::Value becomes
<Json as Format>::Value when F has one bound.
Inherent methods
An enum often needs methods of its own that only some variants can answer.
#[dispatch] on an inherent impl block forwards each marked method to the
named variants' method of the same name; the body is what every other
variant returns. * names every variant, and the method then has no body:
with = path wraps each forwarded result, call = name forwards to a
method of another name, async methods are awaited, and methods without
the attribute are left alone. The impl block has to be in the enum's
crate, which inherent impls are anyway; a bare #[dispatch] on an enum
registers it for this without dispatching a trait.
What each trait item becomes
| Trait item | Enum impl |
|---|---|
type X; |
type X = <FirstVariant as Trait>::X;, or the X = Type option. Every other variant is checked to agree; a disagreement is reported on that variant. |
const N: T; |
const N: T = <FirstVariant as Trait>::N; |
| method with a receiver | A match over the variants calling <Payload as Trait>::method(payload, args..), with #[inline]. |
| default body | Ignored: forwarded like any other method. |
async fn, unsafe fn |
Arms are awaited; calls are wrapped in unsafe { }. |
argument Self, &Self, &mut Self |
Matched on the same variant as self; different variants give the mismatch expression. |
return Self |
Wrapped back into the same variant; Option<Self> and Result<Self, E> are mapped. |
| method without a receiver | Left to the trait's default body; an error when there is none. |
#[cfg] on a method or variant |
Carried onto the generated method or arm. |
Limits
- Every variant holds exactly one unnamed field,
Name(Payload), and every payload implements the trait. - Return-position
impl Traitcannot be dispatched by amatch. - A
derefvariant cannot take part in a method returningSelf: there is no way to put the result back behind the pointer. - Generic associated types and
self: Typereceivers are not supported. - The types in the trait's signatures are written into the enum's module
and must be in scope there, as for a hand-written impl;
crate::paths are rewritten to the trait's crate,super::andself::paths are not. - Associated consts are taken from the first variant without a check.
Every unsupported shape is reported as one sentence on the offending trait
item or variant; see tests/ui for all of them.
"cannot find macro Shape in this scope" at the enum means the trait is not
marked #[dispatchable], or the trait, which carries the macro, is not
imported where the enum is.
Compatibility
no_std, with no dependencies beyond the proc-macro crate. The minimum
supported Rust version is 1.88. Both the crate that owns the trait and the
crate that owns the enum depend on whichever; renaming the dependency is
fine.
License
Licensed under either of Apache License, Version 2.0 or MIT license at your option.