Generics

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 parameter T, used only by the Some(T) variant while None carries no payload.
  • Result<T, E> has two independent type parameters: T for the success payload in Ok(T) and E for the failure payload in Err(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 }