Rust has no exceptions at runtime for expected failures — use Result<T, E> and Option
Core concepts
- Option:
Some(T)orNone— absence of value without null pointers. — The Rust Book: Enums - Result:
Ok(T)orErr(E)— explicit error paths. — The Rust Book: Result - ? operator: Propagate errors from functions returning
Result. — The Rust Book: The ? operator - Traits: Shared behavior —
impl Display for MyType, trait bounds on generics. — The Rust Book: Traits - Generics: Monomorphized at compile time — zero-cost abstractions. — The Rust Book: Generic data types
use std::fs;
fn read_config(path: &str) -> Result<String, std::io::Error> {
let text = fs::read_to_string(path)?;
Ok(text)
}Concept videos
YouTube lessons for these topics. On a large screen they open in the reading panel next to the roadmap, same as the article links on each core concept.
- Rust fundamentals — Rust in 100 Seconds (Fireship)
Explanation
Rust replaces null and exceptions with enum-shaped honesty — every failure path is visible in types.
Option
Option models missing data without null dereference. Unwrap only when you can prove presence; prefer match, if let, or combinators (map, and_then) in production code.
Result
Result separates success from failure values. Callers must handle Err — the compiler nags until you do. This makes I/O and parsing flows explicit in signatures.
? operator
? early-returns errors, like throw but typed. It only works in functions returning Result or Option. Chains of fallible steps read linearly instead of nested matches.
Traits
Traits are shared capabilities — printing, cloning, comparison. Implement traits for your types to plug into generic algorithms and standard library APIs.
Generics
Generics compile to specialized code per concrete type — no runtime vtable unless you use trait objects. You pay for abstraction without hidden allocations when monomorphized.
What Is It?
Traits define shared behavior; generics parametrize types and traits bounds.
Result<T,E> models success/failure without exceptions for expected cases.
The ? operator forwards errors up the call stack cleanly.
Why Should I Care?
Idiomatic Rust libraries expose composable traits (Iterator, From, Display).
Error handling patterns appear in every Axum handler and CLI tool.
Real World Analogy
Traits are electrical outlet standards — any device (type) that implements the plug shape (trait) can connect to the wall (generic function).
Visual Mental Model
impl Trait for Type
fn foo<T: Trait>(t: T)Beginner Explanation
Use Result for recoverable errors, Option for missing values, and ? to propagate errors in functions returning Result.
Smallest Possible Example
fn parse_id(s: &str) -> Result<u32, std::num::ParseIntError> {
s.parse()
}Common Mistakes
❌ Using unwrap() in library code paths.
Panics crash the process — return Result to callers.
❌ Over-genericizing before you need it.
Start concrete; extract generics when patterns repeat.
❌ Mixing String and &str errors inconsistently.
Pick error types (anyhow, thiserror) for apps vs libraries.
Real Project Example
serde uses traits to serialize structs to JSON across the ecosystem.
reqwest returns Result for HTTP failures — standard in services.
Build This Mini Project
Write a small CLI that reads a file path arg, uses ? for IO errors, and maps parse errors to user-friendly messages.
Interview Question
Result vs panic?
Result handles expected failures you want callers to recover from. panic! is for programmer bugs or unrecoverable states — avoid in hot paths serving traffic.
Key Takeaways
- Traits enable polymorphism without inheritance.
- Prefer Result over unwrap in production paths.
- Use ? for ergonomic propagation.
- Option models absence; Result models failure.
Resources
The 60-second story
A post office that refuses to use a magic “this letter vanished” stamp. If a value might be missing, the envelope says Option: Some(letter) or None. If the work might fail in a way you are supposed to handle, the envelope says Result: Ok(value) or Err(reason). Exceptions that jump out of the building are for “the building is on fire,” not for “this mailbox was empty.”
The ? operator is the clerk who tries to pass the envelope onward and, if it is an error, walks it straight back to the sender without a speech. You still have a Result at the edges. You just stop indenting five levels of “if err return err.”
Traits are job descriptions. Display means “this type can introduce itself as text.” You do not inherit a whole family. You say impl Display for MyType and you take that one job. Generics are a mail slot that says “any envelope, as long as it has this job.” The compiler then builds a specific slot per concrete type. That is monomorphization: no surprise lookup at the counter for the common case. You pay at compile time so the counter stays quick.
Map the office.
Optionis “maybe a letter, never a null ghost.”Resultis “the work succeeded or here is the reason.”?is “bounce errors back without a speech.”- A trait is one job description.
- A generic with a trait bound is “any type that can do this job.”
- Monomorphization is printing a specific form per type instead of one vague form.
fn parse_port(text: &str) -> Result<u16, std::num::ParseIntError> {
let port = text.parse::<u16>()?;
Ok(port)
}If parse fails, ? returns the error. If it works, you wrap the port in Ok. Callers cannot ignore the envelope. That is the design.
Rust split Option and Result because null plus exceptions made two different kinds of absence look like one crash. Traits exist so you can share behavior without a giant parent class that also knows how to sing. Generics exist so that sharing does not require throwing the type away and hoping.
Mental model: envelopes that say what is inside, including bad news. Missing is None. Failure you should handle is Err. A trait is a job, not a family tree.
You can explain the page if you refuse to call unwrap() “error handling” and you can say ? just forwards the bad envelope.

