Today was all about understanding enums, how they differ from structs, how they model real-world data better in some cases, and why Option is Rust’s safe alternative to null.
📚 What Are Enums?
While structs let you group multiple pieces of data, enums let you express a value that could be one of many types.
Think of enums as a way to say: "This value is either this, that, or something else."
enum IpAddrKind {
V4,
V6,
}
With this, you can create variables like:
let home = IpAddrKind::V4;
let loopback = IpAddrKind::V6;
All values created with the enum are of the same type (IpAddrKind), even though they hold different "meanings".
đź§ Enums vs Structs
You can achieve the same goal using structs and enums. For example:
struct IpAddr {
kind: IpAddrKind,
address: String,
}
But with enums, you can directly associate data with each variant:
enum IpAddr {
V4(String),
V6(String),
}
Or even mix types:
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
This keeps your data structure concise and flexible.
đź§° Enums with Data
Rust enums are powerful because variants can:
- Contain different types of data.
- Contain different amounts of data.
Example:
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
Here:
- Quit is like a unit struct (no data),
- Move acts like a named struct,
- Write is a tuple struct with one value,
- ChangeColor is a tuple struct with three values.
You could also use four separate structs—but then you’d have to write separate logic for each. With enums, you get one unified type.
đź§ Defining Methods on Enums
Just like structs, you can define methods on enums using impl.
impl Message {
fn call(&self) {
println!("Called: {:?}", self);
}
}
And use it like:
let m = Message::Write(String::from("hello"));
m.call();
🔎 The Option Enum
Rust doesn’t have null. Instead, it has the Option enum:
enum Option<T> {
Some(T),
None,
}
This is Rust’s way of saying: "The value might be something or nothing."
Examples:
let some_number = Some(5); // Option<i32>
let some_char = Some('e'); // Option<char>
let absent_number: Option<i32> = None;
This avoids a billion-dollar problem of null values that plague other languages.
đźš« Option Prevents Dangerous Assumptions
Rust won’t let you do this:
let x: i8 = 5;
let y: Option<i8> = Some(5);
let sum = x + y; // ❌ ERROR
Why? Because Option and i8 are different types. You must explicitly handle the case when a value might be None.
This forces you to safely unwrap or pattern match:
let y: Option<i8> = Some(5);
match y {
Some(n) => println!("Got: {}", n),
None => println!("No value!"),
}
🆚 Option vs Null (Why It’s Better)
- Null is unchecked: you often forget to handle it, leading to runtime crashes.
- Option is checked: the compiler forces you to handle the None case.
- With Option, you’re explicit about the possibility of absence.
- This makes your code more reliable, less error-prone, and easier to reason about.
đź§© Enums in the Standard Library
The Rust standard library contains many useful enums, like:
enum Result<T, E> {
Ok(T),
Err(E),
}
Or the IpAddr type:
enum IpAddr {
V4(Ipv4Addr),
V6(Ipv6Addr),
}
You can use any data in enums: primitives, structs, or even other enums!
✍️ Summary
Here’s what I covered on Day 7:
- Enums allow expressing one of many possible states or variants.
- You can attach data of varying types to enum variants.
- Enums are more appropriate than structs in many cases (like IpAddr).
- You can define methods on enums using impl, just like structs.
- Rust replaces unsafe null values with the safe and expressive Option.
- Compiler forces you to handle the absence of values properly.
- Option and pattern matching are key to writing robust Rust code.
Rust’s enums feel like algebraic data types from functional programming—powerful, precise, and safe. This was a big shift in thinking from JavaScript-style nullables, and I’m loving it.
Top comments (0)