Overview
6 min read
In TypeScript you signal failure by throwing and recover with try/catch, and you represent “nothing” with null/undefined. Rust has no exceptions and no null: a fallible function returns a Result<T, E>, an optional value is an Option<T>, and the ? operator propagates failures upward in one character. This section maps every TypeScript error-handling habit onto its idiomatic Rust counterpart, then goes deeper into panics, custom error types, the std::error::Error trait, and the two crates that make real-world error handling ergonomic — anyhow for applications and thiserror for libraries (current 1.x/2.x APIs, compile-verified).
What You’ll Learn
Section titled “What You’ll Learn”- Why Rust replaces
throw/try/catchwith theResult<T, E>return value andnull/undefinedwithOption<T>— failure and absence become part of the type, and the compiler refuses to let you ignore them - How to choose between
ResultandOption, and how to extract their inner values withmatch,if let,let ... else, and combinators (map,unwrap_or_else,ok_or_else) - How the
?operator propagates anErr/Noneto the caller, and how its built-inFrom-based conversion lets one function unify several underlying error types - The difference between a recoverable error (a
Result) and an unrecoverable one (apanic!), how unwinding differs from aborting, and when reaching forpanic!is actually the right call - When
unwrap/expectare acceptable (tests, prototypes, provable invariants) and when they reintroduce exactly the runtime crashesResultwas meant to prevent — plus how to write a goodexpectmessage - How to design custom error types as enums or structs, and implement
Displayandstd::error::Errorso they behave like first-class errors - What the
Errortrait requires (Display+Debug), how thesource()cause chain works, and howBox<dyn Error>type-erases any error behind one return type - How to use
anyhow1.x (Context,anyhow!,bail!) in applications andthiserror2.x (#[derive(Error)],#[from],#[error("...")]) in libraries — the current, idiomatic APIs - How to handle multiple error types in one function — by erasing to
Box<dyn Error>or aggregating into an enum with#[from]conversions - The design decisions that tie it all together: libraries vs. applications, error granularity, message quality, and where recoverable handling ends and a programmer bug begins
Topics
Section titled “Topics”| Topic | Description |
|---|---|
| Result and Option | try/catch & throw → Result<T, E> and null/undefined → Option<T>: the difference between them and how to match on each. |
The ? Operator | The ? operator for propagation, From-based error conversion, and using ? in a function (or main) that returns Result/Option. |
| Panicking | throw → panic!: unwinding vs. aborting, when panics are appropriate (unrecoverable failures and bugs), and panic! vs. Result. |
unwrap and expect | unwrap/expect and when they are acceptable (tests, prototypes, provable invariants), plus how to write a useful expect message. |
| Custom Error Types | Defining custom error types as enums or structs and implementing the Display and Error traits by hand. |
The Error Trait | std::error::Error: the Display + Debug requirements, the source() cause chain, and Box<dyn Error>. |
anyhow & thiserror | anyhow 1.x for applications (Context, anyhow!) and thiserror 2.x for libraries (#[derive(Error)], #[from]) — current APIs, compile-verified. |
| Handling Multiple Error Types | Combining several error types in one function: Box<dyn Error>, enum aggregation, and #[from] conversions. |
| Error-Handling Best Practices | Error design: libraries vs. applications, when to use which tool, granularity, message quality, and recoverable vs. unrecoverable. |
Learning Objectives
Section titled “Learning Objectives”By the end of this section, you will be able to:
- Translate a TypeScript function that
throws into one that returnsResult<T, E>, and aUser | undefinedreturn intoOption<T>, then handle both withmatch,if let,let ... else, or combinators - Decide between
ResultandOptionfor a given failure, and between aStringerror, a custom enum,Box<dyn Error>, andanyhow::Errorfor theE - Use the
?operator to write straight-line happy-path code, and add theFromimpls (by hand or via#[from]) that let?convert error types automatically - Explain why a
panic!is not a TypeScriptthrow, and reserve panics for unrecoverable bugs while returningResultfor anything a caller could handle - Justify each
unwrap/expectin your code, replacing the rest with?,match, or theunwrap_or_*family, and turn on Clippy’sunwrap_used/expect_usedlints where appropriate - Define a custom error type, implement
Displayandstd::error::Error(includingsource()), and box it behindBox<dyn Error>when type erasure is the right trade-off - Pick
thiserrorfor a library’s precise, matchable error enum andanyhowfor an application’s “propagate-and-report” flow, and add.context(...)to make failures diagnosable
Prerequisites
Section titled “Prerequisites”- Section 06: Data Structures —
ResultandOptionare ordinary enums, and you handle them with pattern matching (match,if let,let ... else). Be comfortable with enums,Option<T>, andimplblocks before starting here. - Section 05: Ownership — error values are owned and moved like any other data;
?returns (and therefore moves) an error out of a function, andBox<dyn Error>is a heap-owned trait object. - Section 02: Basics — concrete types,
#[derive(Debug)], and{:?}/{}formatting (DisplayvsDebug) show up on every page. - Helpful but not required: Section 04: Control Flow for
matchand earlyreturn. A few topics preview Section 09: Generics and Traits — the<T, E>and theFrom/Errortraits behindResultand?— but each page explains what it needs as it goes.
Estimated Time
Section titled “Estimated Time”- Reading: 4-5 hours
- Hands-on Practice: 3-4 hours
- Exercises: 3 hours
- Total: 10-12 hours
Tip: Read the topics in order. Start with the mechanics —
result-option→question-mark— because everything else builds onResult,Option, and?. Then learn the failure modes (panic→unwrap-expect), then how to design error types (custom-errors→error-trait→anyhow-thiserror→multiple-errors), and finish withbest-practicesto tie the choices together. The single biggest mental shift for a TypeScript developer is that failure is a value, not a jump: a thrown exception unwinds the stack invisibly, whereas a RustResultis returned normally and the?operator makes each propagation point explicit in the source.
Next: Section 09: Generics and Traits → — the <T, E> type parameters and the From/Error traits that power Result and the ? operator, generalized into Rust’s full generics-and-traits system.