Have you ever spent hours debugging code because a variable could hold impossible states? In Rust, that doesn't happen. We have been building real systems for almost a decade – from accounting to low-level programming – and algebraic data types make a tangible difference. This isn't academic theory: it's the most robust way to model complex data without runtime surprises.
The problem we solve: illegal states and null pointers
In languages with class hierarchies, you often end up with enums that are just numbers and objects that can be null. Every access is a gamble. We at Meteora Web have managed the ERP of a clothing store: imagine an item that can be “In Stock”, “On Display”, “Sold”, or “Returned”. In Java or Python you'd model it with a string field and many ifs. In Rust you use an enum and the compiler checks everything.
Struct: fine-grained data aggregation
Structs are the most common way to group related fields. Unlike classes, there is no inheritance: you compose, not inherit. This avoids fragile hierarchies.
struct Article {
id: u32,
name: String,
price: f64,
status: ArticleStatus,
}
enum ArticleStatus {
InStock,
OnDisplay { shelf: u8, row: u8 },
Sold { date: String, customer: String },
Returned { reason: String, refund: f64 },
}
Notice how each variant of ArticleStatus can carry different data. This is an algebraic sum type. The compiler forces you to handle every case when you pattern match.
Sponsored Protocol
Pattern matching: destructure data with guarantees
Pattern matching is the tool that makes algebraic types powerful. With match and if let you can extract data and control flow without ever leaving a case uncovered.
fn handle_article(art: &Article) {
match &art.status {
ArticleStatus::InStock => println!("Article {} available", art.name),
ArticleStatus::OnDisplay { shelf, row } => {
println!("Article {} on display: shelf {}, row {}", art.name, shelf, row);
},
ArticleStatus::Sold { date, customer } => {
println!("Sold on {} to {}", date, customer);
},
ArticleStatus::Returned { reason, refund } => {
println!("Returned for '{}' - refund ${}", reason, refund);
},
}
}
If you add a new variant to ArticleStatus, the compiler warns you about every match that doesn't handle it. This eliminates runtime bugs. We call it “the compiler as a free code reviewer”.
Sponsored Protocol
Enums with data: much more than a list of variants
Rust enums are algebraic sum types: each variant can contain data of arbitrary types, even other enums or structs. This allows modeling recursive structures like trees.
enum Expression {
Number(f64),
Sum(Box<Expression>, Box<Expression>),
Product(Box<Expression>, Box<Expression>),
Variable(String),
}
fn evaluate(expr: &Expression, vars: &HashMap<String, f64>) -> f64 {
match expr {
Expression::Number(n) => *n,
Expression::Sum(a, b) => evaluate(a, vars) + evaluate(b, vars),
Expression::Product(a, b) => evaluate(a, vars) * evaluate(b, vars),
Expression::Variable(name) => *vars.get(name).unwrap_or(&0.0),
}
}
Here Box is needed because enums have a fixed size. This is a typical pattern for recursive structures. Pattern matching safely destructures the expression.
Advanced pattern matching: guards, binding, and refutability
Pattern matching goes beyond simple cases. You can add conditions with if (guards), capture references with ref, and work with irrefutable (always true) or refutable (can fail) patterns.
Sponsored Protocol
fn classify_price(price: f64) -> &'static str {
match price {
p if p < 10.0 => "Budget",
p if p < 50.0 => "Mid-range",
p if p < 200.0 => "Premium",
_ => "Luxury",
}
}
Warning: matches must be exhaustive. If you don't cover all variants, the compiler complains. This forces you to think about every possibility.
The if let construct
When you want to handle only one specific case and ignore the rest, if let is more compact than match.
if let ArticleStatus::Sold { date, customer } = &article.status {
println!("Sold to {} on {}", customer, date);
} else {
println!("Not yet sold");
}
It's syntactic sugar for a match with a single arm and _ for the rest.
Algebraic types and performance: zero runtime overhead
We often hear: “doesn't all this control slow down the program?” No. Pattern matches are compiled into simple conditional jumps. The compiler generates machine code as efficient as hand-written if-else chains, but with static guarantees. We have measured it on logging systems and parsers: stack enums with pattern matching are often faster than dynamic class hierarchies.
Sponsored Protocol
Common mistakes to avoid
- Using the wildcard
_when you should list variants explicitly: if your enum has no data variants, listing them explicitly means the compiler will warn you when you add a new one. Use_only when you truly want to ignore unknown future variants (rarely a good idea). - Using
if letwhen you also need the else branch: often a match is clearer. - Pattern matching on references: when matching a reference, your patterns must use
&or userefto bind.
let r = &ArticleStatus::InStock;
match r {
&ArticleStatus::InStock => println!("in stock"),
// alternatively
ArticleStatus::InStock => println!("in stock"), // auto-deref
_ => (),
}
How to integrate struct enum pattern matching in your project
Here's a checklist for designing your algebraic types:
- Identify all possible states of an entity (e.g., order: pending, processing, shipped, delivered, cancelled).
- Create an enum with one variant per state, including only the data needed for that state.
- Use
matcheverywhere you need to react to the state. The compiler will force you to handle every case. - When the state changes, construct a new enum instance – never mutate a “status” field randomly.
In summary – what to do now
- Rewrite a state machine in another language to Rust enums: take existing code using flags or strings for state and convert it to an enum. Watch nested ifs disappear.
- Practice modeling a tree (e.g., mathematical expressions, file system) with recursive enums. Use pattern matching to traverse it.
- Enable compiler warnings on unused variants (
#![deny(unused_variants)]) – it will force you to clean up code. - Read the official documentation on Enums and Pattern Matching for deeper understanding.
We at Meteora Web have built e-commerce platforms and logging systems using these principles. When the compiler says OK, we can sleep soundly. Algebraic data types are not an academic luxury: they are the safety net every professional developer deserves.