TL;DR: Rust enums are algebraic data types where each variant can optionally hold different types of data. Unlike enums in C, Java, or TypeScript (which are just named integers or string unions), Rust enums can carry payloads. This makes them ideal for modeling states, errors, and optional values.
Option<T>andResult<T, E>; the two most important types in Rust; are both enums.
What Is an Enum in Rust?
A Rust enum defines a type that can be one of several variants, where each variant can optionally hold different data; making enums the primary tool for modeling sum types (also called tagged unions or discriminated unions).
enum Shape {
Circle(f64), // variant with one value (radius)
Rectangle(f64, f64), // variant with two values (width, height)
Triangle { base: f64, height: f64 }, // variant with named fields
Point, // variant with no data
}Each Shape value is exactly one of these variants; the enum guarantees exhaustive representation of all possibilities.
How Do You Use Enums With Pattern Matching?
Enums are used with match expressions that destructure each variant; the compiler requires you to handle every variant, preventing missed cases.
fn area(shape: &Shape) -> f64 {
match shape {
Shape::Circle(r) => std::f64::consts::PI * r * r,
Shape::Rectangle(w, h) => w * h,
Shape::Triangle { base, height } => 0.5 * base * height,
Shape::Point => 0.0,
}
}
fn main() {
let c = Shape::Circle(5.0);
println!("Area: {:.2}", area(&c)); // Area: 78.54
}If you add a new variant to Shape without updating the match, the compiler produces an error; impossible to forget a case.
What Is Option<T>?
Option<T> is Rust's built-in enum for representing values that may or may not be present; replacing null pointers from other languages.
enum Option<T> {
Some(T), // value is present
None, // value is absent
}fn find_user(id: u64) -> Option<String> {
if id == 1 {
Some("Alice".to_string())
} else {
None
}
}
fn main() {
match find_user(1) {
Some(name) => println!("Found: {name}"),
None => println!("User not found"),
}
// Shorthand methods:
let name = find_user(1).unwrap_or("Unknown".to_string());
let upper = find_user(1).map(|n| n.to_uppercase());
}Option eliminates null pointer exceptions; Rust has no null. Every potentially absent value is explicit in the type system.
What Is Result<T, E>?
Result<T, E> is Rust's built-in enum for operations that can succeed or fail; the foundation of error handling in Rust.
enum Result<T, E> {
Ok(T), // success with value
Err(E), // failure with error
}use std::num::ParseIntError;
fn parse_port(s: &str) -> Result<u16, ParseIntError> {
let n: u16 = s.parse()?; // ? propagates errors automatically
Ok(n)
}
fn main() {
match parse_port("8080") {
Ok(port) => println!("Listening on port {port}"),
Err(e) => println!("Invalid port: {e}"),
}
}The ? operator is syntactic sugar for early return on Err; it makes error propagation concise without hiding failures.
How Are Enums Different From Other Languages?
In most languages, enums are named constants (Java, C) or string/number unions (TypeScript). Rust enums are algebraic data types; each variant is a distinct type that can carry arbitrary data.
| Feature | C enum | Java enum | TypeScript union | Rust enum |
|---|---|---|---|---|
| Named variants | ✅ | ✅ | ✅ | ✅ |
| Variants hold data | ❌ | Limited | ❌ | ✅ |
| Pattern matching | ❌ | ❌ | Partial | ✅ Exhaustive |
| Compiler enforces all cases | ❌ | ❌ | Partial | ✅ |
| Used for error handling | ❌ | ❌ | ❌ | ✅ (Result) |
Frequently Asked Questions
Yes. Use impl EnumName { } the same way you would for structs. Enums can have methods, associated functions, and implement traits.
if let is shorthand for match when you only care about one variant: if let Some(x) = optional { use(x) }. Use it when handling a single variant is all you need; use match when you need to handle multiple variants.
Yes. #[derive(Debug, Clone, PartialEq)] works on enums the same as structs. #[derive(serde::Serialize, serde::Deserialize)] makes enums serializable to JSON, TOML, etc.
Marking an enum #[non_exhaustive] tells downstream crates that new variants may be added in future versions. This forces them to include a wildcard _ arm in match expressions, preventing breakage when variants are added.
Sources
Related Glossary Terms
- Option: Rust's null-safety enum
- Result: Rust's error handling enum
- Trait: Enums can implement traits
- if let:
if letis one of the most common ways to match a single enum variant - Pattern Matching: Enums are the core data type that makes Rust pattern matching powerful
Keep Reading
- Rust Error Handling: Result and Option: Result and Option are the two most important enums in Rust
- Rust Ownership and Borrowing Explained: enum variants can own data
- Rust for Python Developers: Python has no algebraic data types; enums fill that gap
