Handling enums
Structs aren’t the only thing you can derive on. Enums are just as common — serde,
strum, and thiserror all derive heavily on them. Where a struct has fields, an enum has
variants, and each variant can itself carry named, unnamed, or no fields.
DataEnum and variants
When the input is an enum, DeriveInput::data is Data::Enum(DataEnum). The piece you care
about is the list of variants:
#![allow(unused)]
fn main() {
pub struct DataEnum {
pub variants: Punctuated<Variant, Comma>,
// ...
}
}
Each Variant has:
ident— the variant’s name (Circle,Point, …)fields— asyn::Fields, exactly the same enum you met for structsdiscriminant— the= 1part ofenum E { A = 1 }, if present
The key realisation: a variant’s fields is the same Fields type as a struct’s body. A
variant can be a unit (Point), a tuple (Circle(f64)), or a struct variant
(Rectangle { w: f64, h: f64 }) — and you handle them with the same three-way match.
Generating a match arm per variant
Most enum derive macros generate a match self { .. } with one arm per variant. The tricky
part is writing a pattern that binds — or ignores — the variant’s payload. If you only need to
know which variant you’re looking at, you can ignore the payload entirely:
#![allow(unused)]
fn main() {
let name = &input.ident; // the enum's name
let arms = data.variants.iter().map(|variant| {
let vname = &variant.ident;
let vstr = vname.to_string();
// A pattern that matches the variant and ignores any data it carries.
let pattern = match &variant.fields {
Fields::Unit => quote! { #name::#vname },
Fields::Unnamed(_) => quote! { #name::#vname(..) },
Fields::Named(_) => quote! { #name::#vname { .. } },
};
quote! { #pattern => #vstr, }
});
quote! {
impl #name {
pub fn as_str(&self) -> &'static str {
match self {
#(#arms)*
}
}
}
}
}
Note the three pattern shapes — Foo, Foo(..), and Foo { .. } — must match the variant’s
declared shape exactly, or the generated match won’t compile. .. is doing the work of
ignoring the payload in the tuple and struct cases.
If instead you needed to use the payload — say, to forward a field — you’d bind it instead of ignoring it, and reference those bindings in the arm body:
#![allow(unused)]
fn main() {
match &variant.fields {
Fields::Unit => quote! { #name::#vname => 0, },
// Bind the first tuple field as `x` and use it in the body.
Fields::Unnamed(_) => quote! { #name::#vname(x, ..) => x.count(), },
// Bind named fields by name.
Fields::Named(_) => quote! { #name::#vname { w, h, .. } => w * h, },
}
}
The exercises in this workshop all ignore the payload, but binding it is the same Fields
match — you just write Foo(x) / Foo { w, h } instead of Foo(..) / Foo { .. }.
Exercise
The exercise for this section is located in 04_derive/02_enums
The as_str macro above is your worked example. For the exercise you’ll write a sibling macro,
Ordinal, that generates an ordinal(&self) -> usize method returning the variant’s 0-based
position in declaration order (first variant 0, second 1, …).
The outer match/impl scaffolding is provided; you fill in each arm. Two things to work out:
the arm body is now the variant’s index (reach for enumerate), and you still have to build a
payload-ignoring pattern whose shape depends on the variant — apply the same Fields matching the
as_str example uses.