Enums & Pattern Matching
Chapter 5 covered structs. This chapter covers Rust's other major custom type — and it's genuinely more powerful than what Go calls an "enum," which is really just a sequence of typed integer constants built with iota.
Enums That Carry Data
A Rust enum variant can hold its own associated data — and different variants can hold entirely different shapes of data. Go's iota-based constants are just named integers; there's no way to attach data to one at all.
V4 and V6 aren't just labels — each is effectively its own little data structure, holding whatever shape of data actually fits that variant.
The match Expression
Rust's match is a switch-like construct, but stricter and more useful: it's an expression (it produces a value), and it must be exhaustive — every possible variant needs a matching arm, or an explicit _ catch-all. Missing a case is a compile error, not silent fall-through the way an incomplete switch in Go or JavaScript can behave.
Each arm destructures the variant's data directly — IpAddr::V4(a, b, c, d) pulls all four numbers out in one line, ready to use immediately.
Option<T>: No Null At All
Rust has no null or nil value at all — the concept doesn't exist in the language. Anything that might be absent is instead wrapped in the built-in Option<T> enum:
You cannot accidentally treat a None as if it were a real value — the compiler forces every Option<T> to be handled (via match, and later if let/unwrap) before the real value inside a Some can be used at all.
| Go | Rust | |
|---|---|---|
| "Enum" | iota — just typed integer constants | Variants can each carry their own data |
| Missing value | nil / zero value | Option<T> — Some(T) or None |
| What happens if mishandled | Nil pointer dereference — a runtime panic | Compile error — must handle None before compiling |
Option<T> is Rust's structural answer: since null doesn't exist as a concept at all, an entire category of bugs — null pointer/reference exceptions — simply cannot occur, caught instead as a compile-time requirement to handle absence explicitly.
match elsewhere in a codebase fail to compile, if none of them have a catch-all _ arm. This can feel like a nuisance at first — but it's the compiler doing exactly its job: pointing at every single place in the code that needs to be updated to actually handle the new case, rather than letting it silently fall through unhandled somewhere far from where the variant was added.
Coding Challenges
Define an enum Shape with variants Circle(f64) (radius) and Rectangle(f64, f64) (width, height). Write a match expression that computes the area for either variant.
📄 View solutionWrite a function find_first_even(numbers: &[i32]) -> Option<i32> that returns the first even number in a slice, or None if there isn't one. Call it with a match that prints either the found number or a "no even number" message.
Explain why Rust's Option<T> prevents a whole category of bugs that Go's nil pointers don't — specifically, at what point each language catches the mistake of using an absent value.
Chapter 6 Quick Reference
- enum variants can carry their own data — unlike Go's iota, which produces plain integer constants
- match is exhaustive — every variant needs an arm, or an explicit
_catch-all, enforced at compile time - match arms can destructure a variant's data directly, ready to use in the arm's body
- Option<T> — Rust's built-in replacement for null:
Some(T)orNone - The compiler forces every
Option<T>to be handled before the real value can be used — no null pointer exceptions possible - Go's nil pointer dereference is a runtime panic; Rust's equivalent mistake is a compile-time error
- A match that breaks after adding a new enum variant is the compiler correctly flagging every spot that needs updating
- Next chapter: Error Handling —
Result<T, E>, the?operator, andpanic!vs. recoverable errors