432: Macro Enum Dispatch
Tutorial Video
Text description (accessibility)
This video demonstrates the "432: Macro Enum Dispatch" functional Rust example. Difficulty level: Fundamental. Key concepts covered: Functional Programming. Enums with many variants often need repetitive match arms for dispatch methods. Key difference from OCaml: 1. **Exhaustiveness**: Both Rust and OCaml require exhaustive match arms; adding a variant to a `dispatch_enum!`
Tutorial
The Problem
Enums with many variants often need repetitive match arms for dispatch methods. A Command enum with 20 variants requires a 20-arm match for each dispatch method. When variants map uniformly to trait implementations, macros can generate all the match arms automatically. This is the enum_dispatch crate's core idea: eliminate the boilerplate of matching each variant and delegating to the inner type. The dispatch_enum! macro generates both the enum and its dispatch methods from a compact declaration.
Enum dispatch macros appear in event handling systems, command patterns, codec implementations, and any state machine with many uniform variant behaviors.
🎯 Learning Outcomes
dispatch_enum! generates both enum variants and dispatch methodsstringify!($variant) in match arms provides zero-cost variant namesdyn Trait (open set, flexible)matches! macro simplifies variant-checking predicatesCode Example
#![allow(clippy::all)]
//! Enum Dispatch Macro
//!
//! Generating match arms for enums.
/// Define enum with dispatch method.
#[macro_export]
macro_rules! dispatch_enum {
($name:ident { $($variant:ident),* $(,)? }) => {
#[derive(Debug, Clone, Copy)]
pub enum $name { $($variant,)* }
impl $name {
pub fn name(&self) -> &'static str {
match self {
$(Self::$variant => stringify!($variant),)*
}
}
}
};
}
dispatch_enum!(Action {
Start,
Stop,
Pause,
Resume
});
/// Command pattern with enum.
pub enum Command {
Print(String),
Add(i32, i32),
Exit,
}
impl Command {
pub fn execute(&self) -> String {
match self {
Command::Print(s) => format!("Print: {}", s),
Command::Add(a, b) => format!("Add: {} + {} = {}", a, b, a + b),
Command::Exit => "Exit".to_string(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dispatch_name() {
assert_eq!(Action::Start.name(), "Start");
assert_eq!(Action::Stop.name(), "Stop");
}
#[test]
fn test_command_print() {
let c = Command::Print("Hello".into());
assert!(c.execute().contains("Hello"));
}
#[test]
fn test_command_add() {
let c = Command::Add(2, 3);
assert!(c.execute().contains("5"));
}
#[test]
fn test_command_exit() {
let c = Command::Exit;
assert_eq!(c.execute(), "Exit");
}
#[test]
fn test_all_actions() {
let actions = [Action::Start, Action::Stop, Action::Pause, Action::Resume];
assert_eq!(actions.len(), 4);
}
}Key Differences
dispatch_enum!-generated enum requires updating all dispatch methods.stringify!($variant) gives the variant name as a string automatically; OCaml needs explicit string mappings or deriving.dyn Trait in Rust and first-class modules in OCaml handle open sets.OCaml Approach
OCaml handles enum dispatch through algebraic types and pattern matching. A type command = Print of string | Add of int * int | Exit with let execute = function Print s -> ... | Add (a,b) -> ... | Exit -> ... is idiomatic. OCaml's exhaustiveness checking ensures all variants are handled. Adding a new variant breaks existing match expressions — a feature (forces updates) or a bug (breaks compatibility).
Full Source
#![allow(clippy::all)]
//! Enum Dispatch Macro
//!
//! Generating match arms for enums.
/// Define enum with dispatch method.
#[macro_export]
macro_rules! dispatch_enum {
($name:ident { $($variant:ident),* $(,)? }) => {
#[derive(Debug, Clone, Copy)]
pub enum $name { $($variant,)* }
impl $name {
pub fn name(&self) -> &'static str {
match self {
$(Self::$variant => stringify!($variant),)*
}
}
}
};
}
dispatch_enum!(Action {
Start,
Stop,
Pause,
Resume
});
/// Command pattern with enum.
pub enum Command {
Print(String),
Add(i32, i32),
Exit,
}
impl Command {
pub fn execute(&self) -> String {
match self {
Command::Print(s) => format!("Print: {}", s),
Command::Add(a, b) => format!("Add: {} + {} = {}", a, b, a + b),
Command::Exit => "Exit".to_string(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dispatch_name() {
assert_eq!(Action::Start.name(), "Start");
assert_eq!(Action::Stop.name(), "Stop");
}
#[test]
fn test_command_print() {
let c = Command::Print("Hello".into());
assert!(c.execute().contains("Hello"));
}
#[test]
fn test_command_add() {
let c = Command::Add(2, 3);
assert!(c.execute().contains("5"));
}
#[test]
fn test_command_exit() {
let c = Command::Exit;
assert_eq!(c.execute(), "Exit");
}
#[test]
fn test_all_actions() {
let actions = [Action::Start, Action::Stop, Action::Pause, Action::Resume];
assert_eq!(actions.len(), 4);
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dispatch_name() {
assert_eq!(Action::Start.name(), "Start");
assert_eq!(Action::Stop.name(), "Stop");
}
#[test]
fn test_command_print() {
let c = Command::Print("Hello".into());
assert!(c.execute().contains("Hello"));
}
#[test]
fn test_command_add() {
let c = Command::Add(2, 3);
assert!(c.execute().contains("5"));
}
#[test]
fn test_command_exit() {
let c = Command::Exit;
assert_eq!(c.execute(), "Exit");
}
#[test]
fn test_all_actions() {
let actions = [Action::Start, Action::Stop, Action::Pause, Action::Resume];
assert_eq!(actions.len(), 4);
}
}
Deep Comparison
OCaml vs Rust: macro enum dispatch
See example.rs and example.ml for side-by-side implementations.
Key Points
Exercises
dispatch_enum! to generate a Format { Json, Csv, Toml, Binary } enum with a content_type() -> &'static str method returning the MIME type for each format.ErrorCategory { Network, Io, Parse, Permission, Timeout } with dispatch methods is_retryable() -> bool and log_level() -> &'static str. Implement these with different behavior per variant.dispatch_data!( Message { Text(String) => fn len -> String::len(data), Binary(Vec<u8>) => fn len -> data.len() } ).