Lesson 9
Enums
Define enums with variants that can hold data, and explore the powerful Option<T> type.
Enums: More Than Just Labels
In most languages, enums are just named constants. In Rust, enum variants can carry data. Each variant can hold different types and amounts of data. This makes Rust enums incredibly powerful -- they're closer to "tagged unions" or "sum types" from functional programming.
enum Shape {
Circle(f64), // holds radius
Rectangle(f64, f64), // holds width, height
Triangle(f64, f64, f64), // holds three sides
}
Option: No More Null
Rust has no null. Instead, it uses the Option<T> enum to represent a value that might not exist. It has two variants: Some(value) and None. The compiler forces you to handle the None case, eliminating an entire class of bugs.
Adding Methods
Just like structs, enums can have methods defined in impl blocks. You'll typically use match inside these methods to handle each variant. We'll cover match in depth in the next lesson.
Try it
use std::f64::consts::PI;
enum Shape {
Circle(f64),
Rectangle(f64, f64),
}
impl Shape {
fn area(&self) -> f64 {
match self {
Shape::Circle(radius) => PI * radius * radius,
Shape::Rectangle(width, height) => width * height,
}
}
fn describe(&self) -> String {
match self {
Shape::Circle(r) => format!("Circle with radius {:.1}", r),
Shape::Rectangle(w, h) => format!("Rectangle {}x{}", w, h),
}
}
}
fn main() {
let shapes = vec![
Shape::Circle(5.0),
Shape::Rectangle(4.0, 6.0),
Shape::Circle(1.0),
];
for shape in &shapes {
println!("{}: area = {:.2}", shape.describe(), shape.area());
}
// --- Option<T>: Rust's replacement for null ---
println!("\n--- Option<T> ---");
let numbers = vec![10, 20, 30];
// .get() returns Option<&T>
let first = numbers.get(0);
let missing = numbers.get(99);
println!("First element: {:?}", first);
println!("Missing element: {:?}", missing);
// Handle Option with match
match numbers.get(1) {
Some(value) => println!("Found: {}", value),
None => println!("Nothing there!"),
}
// unwrap_or provides a default
let val = numbers.get(99).unwrap_or(&0);
println!("With default: {}", val);
// A practical function using Option
let result = find_first_even(&[1, 3, 5, 8, 9]);
println!("\nFirst even in [1,3,5,8,9]: {:?}", result);
let no_even = find_first_even(&[1, 3, 5, 7]);
println!("First even in [1,3,5,7]: {:?}", no_even);
}
fn find_first_even(numbers: &[i32]) -> Option<i32> {
for &n in numbers {
if n % 2 == 0 {
return Some(n);
}
}
None
}Challenge
Add a Triangle variant to the Shape enum that holds base and height. Implement its area calculation (0.5 * base * height) in the area() method.
Previous: Structs
Next: Pattern Matching