An enum is a type that represents "exactly one of several shapes." While a struct is AND (field 1 and field 2), an enum is OR (variant A or variant B). Each variant can hold data, allowing you to represent anything from simple labels to complex sum types. The standard library's Option<T> is a representative enum — the secret to Rust having no nulls.
Learning Objectives
- Define multiple variants using
enumand create instances. - Understand the various forms in which a variant can hold data (tuple/struct/unit).
- Attach methods to an enum with
impl. - Learn the meaning of
Option<T>andSome/None, and understand null safety.
Key Concepts
1) Shapes of an enum
enum IpAddr {
V4(u8, u8, u8, u8), //Tuple type
V6(String), //Single field
Loopback, //No data
}
Even within the same enum, the data shape can differ for each variant.
2) Attaching methods with impl
impl Coin {
fn value_in_cents(&self) -> u32 {
match self { /* ... */ }
}
}
match will be covered in detail in the next chapter, but the above pattern is the most common form of enum + methods.
3) Option<T> — null safety
pub enum Option<T> {
None,
Some(T),
}
This type is used precisely in places where a value may or may not exist. Since the fact that "a value may not exist" is directly reflected in the type, users must explicitly handle it with match or if let.
fn first_even(nums: &[i32]) -> Option<i32> {
for &n in nums {
if n % 2 == 0 { return Some(n); }
}
None
}
4) Option Basic Handling Methods
| Method | Behavior |
|---|---|
unwrap() |
Returns value if Some, panics if None (only in safe places) |
unwrap_or(default) |
Default value when None |
unwrap_or_else(|| ...) |
Calculate default value with a closure when None |
map(|x| ...) |
Transform the value inside Some |
and_then(|x| ...) |
Returns another Option when Some (chaining) |
is_some(), is_none() |
Only checks presence |
? operator |
Propagates None to the caller (Chapter 16) |
5) Comparison with C# / Java null
- C# / Java: Almost all reference types can be null, requiring checks every time.
- Rust: By default, no null. Absence must always be indicated with
Option<T>.
This difference completely blocks NullReferenceException / NullPointerException type bugs.
Examples
Example 1 — ex01_basic.rs : IpAddr enum
//Example 12-1: Enum — A type that represents 'one of these'
//
//Each variant can have data, and the shape of the data is also free.
#[derive(Debug)]
enum IpAddr {
V4(u8, u8, u8, u8), //Tuple-shaped data
V6(String), //One field
Loopback, //Variant without data
}
fn main() {
let home = IpAddr::V4(127, 0, 0, 1);
let server = IpAddr::V6(String::from("::1"));
let lb = IpAddr::Loopback;
println!("home = {:?}", home);
println!("server = {:?}", server);
println!("lb = {:?}", lb);
}
Example 2 — ex02_coin.rs : Coin enum and methods
//Example 12-2: Simple enum + method with no data
//
//A typical example of using match. Matching is covered in detail in the next lesson (13).
#[derive(Debug)]
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
impl Coin {
fn value_in_cents(&self) -> u32 {
match self {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
}
fn main() {
let purse = [Coin::Penny, Coin::Nickel, Coin::Dime, Coin::Quarter, Coin::Dime];
let total: u32 = purse.iter().map(|c| c.value_in_cents()).sum();
println!("Wallet = {:?}", purse);
println!("Total = {total} cents");
}
Example 3 — ex03_option.rs : Option
//Example 3 of Chapter 12: Option<T> — null-safe type
//
//Option in the standard library is actually an enum:
// pub enum Option<T> { None, Some(T) }
//The secret of Rust having no null.
fn find_even(nums: &[i32]) -> Option<i32> {
for &n in nums {
if n % 2 == 0 {
return Some(n);
}
}
None
}
fn main() {
let result1 = find_even(&[1, 3, 5, 8, 9]);
let result2 = find_even(&[1, 3, 5]);
//The most basic handling: match
match result1 {
Some(n) => println!("First even: {n}"),
None => println!("No even numbers"),
}
//unwrap_or: default value when None
let v = result2.unwrap_or(-1);
println!("Second result (default -1) = {v}");
//The ? operator is covered in detail in Chapter 16.
}
Common Mistakes
Q. Do enums also need derive(Debug)?
A. Required to output as {:?}. Usually, something like #[derive(Debug, Clone, PartialEq)] is often attached above the enum definition.
Q. Is Option::None different from null in other languages?
A. Yes, it is different. Null requires a check every time it is used as "any reference type can be empty." None is limited to the Option<T> type, so the compiler explicitly shows through the type "this slot can be empty."
Q. Can I use let v = opt.unwrap(); freely?
A. It is dangerous. A panic occurs when it is None. Except for clearly Some positions (e.g., a just-created value), use match/if let/?/unwrap_or.
Q. I want variants to share the same data shape.
A. It is possible. Within the same enum, you can freely mix different shapes for each variant (like IpAddr in Example 1, where V4 is a tuple, V6 has a single field, Loopback has no data).
Summary
enumis a type of "one of several shapes" — each variant can have data.- You can attach methods to an enum with
impl. Option<T>provides null safety as a standard library enum.unwrapshould only be used where safety is obvious; otherwise, usematch/if let/?.
Try it yourself
Solve the assignment below. The reference solution is shown below.