Generic Enums
A generic enum is an enumeration that declares one or more type parameters in angle brackets (<...>) after the enum name so its variants can carry payloads of abstract types.
Standard Library Generic Enums
Rust's two primary value-and-error-handling types, Option<T> and Result<T, E>, are generic enums defined in the standard library:
enum Option<T> {
None,
Some(T),
}
enum Result<T, E> {
Ok(T),
Err(E),
}
Option<T>has one type parameterT, used only by theSome(T)variant whileNonecarries no payload.Result<T, E>has two independent type parameters:Tfor the success payload inOk(T)andEfor the failure payload inErr(E).
Defining Custom Generic Enums
A custom generic enum can mix unit variants, tuple variants, and struct variants that reference one or more of the enum's type parameters:
#[derive(Debug)]
enum AsyncState<T, E> {
Idle,
Loading { progress_percent: u8 },
Ready(T),
Failed { code: u16, error: E },
}
fn describe_state<T: std::fmt::Debug, E: std::fmt::Display>(state: &AsyncState<T, E>) {
match state {
AsyncState::Idle => println!("State: waiting to start"),
AsyncState::Loading { progress_percent } => {
println!("State: loading ({progress_percent}%)")
}
AsyncState::Ready(payload) => println!("State: ready with {payload:?}"),
AsyncState::Failed { code, error } => {
println!("State: failed [{code}] - {error}")
}
}
}
fn main() {
let mut task: AsyncState<Vec<i32>, &str> = AsyncState::Idle;
describe_state(&task);
task = AsyncState::Loading {
progress_percent: 65,
};
describe_state(&task);
task = AsyncState::Ready(vec![10, 20, 30]);
describe_state(&task);
task = AsyncState::Failed {
code: 504,
error: "gateway timeout",
};
describe_state(&task);
}
State: waiting to start
State: loading (65%)
State: ready with [10, 20, 30]
State: failed [504] - gateway timeout
Type Inference for Unit Variants
When constructing a variant that does not use all of the enum's type parameters (such as AsyncState::Idle or Result::Ok(10)), the compiler must still determine every concrete type parameter of the enum. If no later usage or return type constrains the unused parameter, Rust reports E0282:
// ❌ does not compile
enum Outcome<T, E> {
Success(T),
Failure(E),
}
fn main() {
let ok = Outcome::Success(200_u16);
}
error[E0282]: type annotations needed for `Outcome<u16, _>`
--> src/main.rs:8:9
|
8 | let ok = Outcome::Success(200_u16);
| ^^ ------------------------- type must be known at this point
|
help: consider giving `ok` an explicit type, where the type for type parameter `E` is specified
|
8 | let ok: Outcome<u16, E> = Outcome::Success(200_u16);
| +++++++++++++++++
Annotating the variable (let ok: Outcome<u16, String>) or using a turbofish on the enum path (Outcome::<u16, String>::Success(200)) resolves the unknown type parameter.
Recursive Generic Enums
A generic enum whose variant contains another instance of the same enum type must place the recursive field behind an indirection type such as Box<T> or Vec<T> so the compiler can compute a finite stack size:
#[derive(Debug)]
enum BinaryTree<T> {
Empty,
Node {
value: T,
left: Box<BinaryTree<T>>,
right: Box<BinaryTree<T>>,
},
}
impl<T> BinaryTree<T> {
fn leaf(value: T) -> Self {
Self::Node {
value,
left: Box::new(Self::Empty),
right: Box::new(Self::Empty),
}
}
fn count_nodes(&self) -> usize {
match self {
Self::Empty => 0,
Self::Node { left, right, .. } => 1 + left.count_nodes() + right.count_nodes(),
}
}
}
fn main() {
let tree = BinaryTree::Node {
value: "root",
left: Box::new(BinaryTree::leaf("left_child")),
right: Box::new(BinaryTree::Empty),
};
println!("Total nodes: {}", tree.count_nodes());
println!("Tree: {tree:?}");
}
Total nodes: 2
Tree: Node { value: "root", left: Node { value: "left_child", left: Empty, right: Empty }, right: Empty }