Skip to content

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).


  • Why Rust replaces throw/try/catch with the Result<T, E> return value and null/undefined with Option<T> — failure and absence become part of the type, and the compiler refuses to let you ignore them
  • How to choose between Result and Option, and how to extract their inner values with match, if let, let ... else, and combinators (map, unwrap_or_else, ok_or_else)
  • How the ? operator propagates an Err/None to the caller, and how its built-in From-based conversion lets one function unify several underlying error types
  • The difference between a recoverable error (a Result) and an unrecoverable one (a panic!), how unwinding differs from aborting, and when reaching for panic! is actually the right call
  • When unwrap/expect are acceptable (tests, prototypes, provable invariants) and when they reintroduce exactly the runtime crashes Result was meant to prevent — plus how to write a good expect message
  • How to design custom error types as enums or structs, and implement Display and std::error::Error so they behave like first-class errors
  • What the Error trait requires (Display + Debug), how the source() cause chain works, and how Box<dyn Error> type-erases any error behind one return type
  • How to use anyhow 1.x (Context, anyhow!, bail!) in applications and thiserror 2.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

TopicDescription
Result and Optiontry/catch & throwResult<T, E> and null/undefinedOption<T>: the difference between them and how to match on each.
The ? OperatorThe ? operator for propagation, From-based error conversion, and using ? in a function (or main) that returns Result/Option.
Panickingthrowpanic!: unwinding vs. aborting, when panics are appropriate (unrecoverable failures and bugs), and panic! vs. Result.
unwrap and expectunwrap/expect and when they are acceptable (tests, prototypes, provable invariants), plus how to write a useful expect message.
Custom Error TypesDefining custom error types as enums or structs and implementing the Display and Error traits by hand.
The Error Traitstd::error::Error: the Display + Debug requirements, the source() cause chain, and Box<dyn Error>.
anyhow & thiserroranyhow 1.x for applications (Context, anyhow!) and thiserror 2.x for libraries (#[derive(Error)], #[from]) — current APIs, compile-verified.
Handling Multiple Error TypesCombining several error types in one function: Box<dyn Error>, enum aggregation, and #[from] conversions.
Error-Handling Best PracticesError design: libraries vs. applications, when to use which tool, granularity, message quality, and recoverable vs. unrecoverable.

By the end of this section, you will be able to:

  • Translate a TypeScript function that throws into one that returns Result<T, E>, and a User | undefined return into Option<T>, then handle both with match, if let, let ... else, or combinators
  • Decide between Result and Option for a given failure, and between a String error, a custom enum, Box<dyn Error>, and anyhow::Error for the E
  • Use the ? operator to write straight-line happy-path code, and add the From impls (by hand or via #[from]) that let ? convert error types automatically
  • Explain why a panic! is not a TypeScript throw, and reserve panics for unrecoverable bugs while returning Result for anything a caller could handle
  • Justify each unwrap/expect in your code, replacing the rest with ?, match, or the unwrap_or_* family, and turn on Clippy’s unwrap_used/expect_used lints where appropriate
  • Define a custom error type, implement Display and std::error::Error (including source()), and box it behind Box<dyn Error> when type erasure is the right trade-off
  • Pick thiserror for a library’s precise, matchable error enum and anyhow for an application’s “propagate-and-report” flow, and add .context(...) to make failures diagnosable

  • Section 06: Data StructuresResult and Option are ordinary enums, and you handle them with pattern matching (match, if let, let ... else). Be comfortable with enums, Option<T>, and impl blocks 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, and Box<dyn Error> is a heap-owned trait object.
  • Section 02: Basics — concrete types, #[derive(Debug)], and {:?}/{} formatting (Display vs Debug) show up on every page.
  • Helpful but not required: Section 04: Control Flow for match and early return. A few topics preview Section 09: Generics and Traits — the <T, E> and the From/Error traits behind Result and ? — but each page explains what it needs as it goes.

  • 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 mechanicsresult-optionquestion-mark — because everything else builds on Result, Option, and ?. Then learn the failure modes (panicunwrap-expect), then how to design error types (custom-errorserror-traitanyhow-thiserrormultiple-errors), and finish with best-practices to 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 Rust Result is 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.