14.7 When to Panic vs When to Return Result

Alright, let’s cut through the noise. The single most important decision you’ll make when your code hits a snag is this: do we burn the whole house down (panic!) or do we calmly hand the problem back to the caller (Result<T, E>)? Get this right, and your code is robust and a joy to use. Get it wrong, and you’re building a house of cards on a fault line. The golden rule is beautifully simple: Panic when you, the programmer, have made a mistake. Return a Result when the caller might have made a mistake.

14.6 Collecting Results: Vec<Result<T, E>> to Result<Vec<T>, E>

Right, so you’ve got a collection of things, and each of those things might be a success (Ok) or a failure (Err). You’ve been a good Rustacean and modeled this with a Vec<Result<T, E>>. But now you want to work with a Vec<T>—you want all the good stuff, and if any of them failed, you want to bail out with that first error you find. This is such a common desire that the standard library has a one-liner for it. Behold, collect().

14.5 and_then: Chaining Fallible Operations

Right, so you’ve got the basics of Result and ? down. You can handle a single fallible operation gracefully. But let’s be real, software is rarely that simple. You’re almost always dealing with a chain of operations where any one of them could fail. You could write a whole mess of match statements, but we’re better than that. You could use ? a bunch, but that only works if you’re returning the same error type all the way up, which, as we’ll see, isn’t always the case.

14.4 map_err: Converting Between Error Types

Right, so you’ve got your Result<T, E>, and you’ve started chaining things together with the ? operator. It feels like magic until you try to use it across functions that return different error types. Suddenly, the compiler is yelling at you, and you’re staring at a type mismatch, wondering how to make your std::io::Error play nice with your serde_json::Error. This is where map_err enters the chat. Its job is brutally simple: if you have a Result<T, E> (an Ok(T) or an Err(E)), it lets you apply a function only to the error variant, transforming it from type E into some other type F. It leaves the Ok variant completely untouched. Think of it as a dedicated error-type bouncer, checking the Err at the door and giving it a new outfit before it’s allowed into the next part of the club.

14.3 Using ? in main() with Box<dyn Error>

Alright, let’s talk about one of the first real-world problems you’ll hit when you start writing proper Rust applications: main() is a special snowflake. It’s the entry point, and by default, it returns (). That’s great for “hello world,” but the moment you start doing anything that can fail—reading a file, parsing arguments, connecting to a database—you’re stuck. Your functions return Result, but main can’t. So you end up with a forest of .expect() calls, turning your elegant error-handling into a messy series of potential runtime aborts.

14.2 The ? Operator: Propagating Errors Up the Call Stack

Right, let’s talk about the ? operator. You’ve probably written functions that ended up looking like a Russian nesting doll of match statements: fn get_user_file_data(user_id: &str) -> Result<String, io::Error> { let mut file = match File::open("users.txt") { Ok(f) => f, Err(e) => return Err(e), }; let mut contents = String::new(); match file.read_to_string(&mut contents) { Ok(_) => Ok(contents), Err(e) => Err(e), } } This is… fine. It’s correct. It’s also soul-crushingly verbose. We’re spending more lines of code handling the possibility of work than we are doing the actual work. This is the bureaucratic paperwork of programming. The ? operator is our revolt against this. It says, “If this thing is Ok, give me the value inside. If it’s an Err, just stop what you’re doing and return that error right now from the current function.”

14.1 Result<T, E>: Ok(T) and Err(E)

Right, let’s talk about getting it wrong. Because you will. I will. We all do. The mark of a decent program isn’t that it never fails; it’s that it fails well. It doesn’t just vomit a stack trace and die on some poor user’s machine. It says, “Hey, I couldn’t do that thing you asked, and here’s a polite, useful note about why.” This is where Result<T, E> comes in. It’s not just a type; it’s a philosophy. It forces you to acknowledge that operations can fail, and it makes you handle that reality explicitly. No more pretending everything is fine until it isn’t.

24.7 Combining ? Operator with Result Types

Right, so you’ve met the optional chaining operator (?.), that brilliant little piece of syntactic sugar that lets you navigate potential null or undefined without blowing up your entire application. And you’ve probably also been introduced to the idea of Result (or Either) types, a more structured and type-safe way of representing failure than just throwing errors into the void and hoping someone catches them. You might be wondering: “Can I use these two beautiful things together?” The answer is a resounding “Yes, but… oh dear god, be careful.” It’s like using a chainsaw to make a fine wood carving. Powerful, but one wrong move and you’ve accidentally validated a null value as a successful operation. Let’s talk about how to do it right.

24.6 Discriminated Union Error Models

Right, let’s talk about error handling that doesn’t suck. You’ve probably been told a thousand times to “throw errors” and “catch” them. In the small, that’s fine. But at the application level, treating errors as a shocking, exceptional surprise is like being surprised by rain in London. It’s going to happen. The question is, are you prepared with an umbrella, or are you just going to get wet and complain?

24.5 Error Boundaries in TypeScript Applications

Right, error boundaries. You’ve probably been told they’re React’s magic catch-all for errors in your component tree. And you’d be right, but also, like most magic, it’s mostly clever trickery with a few important limitations. I’m here to demystify it. The core idea is simple: a component that acts like a giant try/catch block for its entire subtree. When something in that tree blows up, this component catches the error, logs it, and displays a friendly “something went wrong” UI instead of letting the entire app vanish into the void. It’s your application’s immune system.

24.4 neverthrow and Other Result Libraries

Right, let’s talk about error handling without throwing your toys out of the pram. You’ve probably been told a thousand times, “Don’t use exceptions for control flow!” It’s good advice. Exceptions are like shouting “FIRE!” in a crowded theatre—effective, but disruptive, and you can’t be sure who’s going to handle the panic. In TypeScript, they also completely bypass your type system. A function that throws lies about its return type; it says it returns a string but might instead yeet a WrenchError into your runtime.

24.3 Result Types: Encoding Success and Failure in the Type System

Let’s be honest: you’ve probably handled errors by throwing them. It’s the JavaScript way, and TypeScript inherits it. You call a function, it blows up, you try/catch it. Simple, right? But it’s also incredibly rude. A function that throws is like a guest who, instead of saying “I’m allergic to shellfish,” just vomits on your table. You had no warning, and now you’re left cleaning up the mess. There’s a more polite, more predictable, and frankly, more type-safe way: the Result type. This isn’t a language feature; it’s a pattern. A design pattern where we encode success and failure into our type system, forcing us (and anyone using our code) to handle both outcomes. The compiler becomes our copilot, making it a compile-time error to forget that things can, and will, go wrong.

24.2 Typed Error Classes and the instanceof Pattern

Right, let’s talk about one of the few things that can make error handling in TypeScript feel almost civilized: the instanceof pattern. You see, JavaScript’s native Error type is tragically anemic. It’s a blank slate with a message property and, if you’re lucky, a usable stack trace. Throwing around generic Error objects is like trying to fix a precision watch with a sledgehammer—it gets the job done, but good luck figuring out what went wrong afterwards.

24.1 The try/catch Problem: Thrown Values Are typed as unknown

Right, so you’ve decided to use try/catch in TypeScript. Good for you. It’s the responsible choice. You write your beautiful, type-safe code, you wrap the risky operation, and then you go to handle the error in the catch block. You type e and… wait a minute. What is this unknown nonsense? I can see your face. You were expecting an Error object, or maybe a string if some barbaric library threw one. Instead, TypeScript gives you this cosmic shrug. You feel like you’ve been robbed. “I know this function throws an Error!” you yell at your IDE. The IDE does not care.

20.8 When to Handle vs When to Return Errors

Right, let’s talk about the single most common decision you’ll make when writing Go: do you handle this error right here, or do you kick this problem up the chain for someone else to deal with? This isn’t just academic; getting this wrong is how you end up with either fragile code that crashes on the first hiccup or a sprawling mess of if err != nil blocks that obscure your actual logic.

20.7 Custom Error Types: Adding Structured Information

Right, let’s talk about making your errors actually useful. The built-in error interface is brilliantly simple, but let’s be honest, a string wrapped in an interface is about as informative as a “Something Went Wrong” alert on a vending machine that just ate your last dollar. You know a problem exists, but you have no idea why or what to do about it. That’s where custom error types come in. We’re going to move beyond “file not found” to “file ‘go.mod’ not found in /home/you/project: permission denied”.

20.6 errors.As: Extracting a Specific Error Type from the Chain

Right, so you’ve wrapped an error, and now you’ve got this whole chain of them. It’s like a Russian nesting doll of failure. You know somewhere deep inside this mess is a specific type of error you actually care about—maybe a *os.PathError to check which file it choked on, or a custom TemporaryError you’ve defined to see if you should retry the operation. You could try to manually peel back the layers with a series of errors.Is checks and type assertions, but that’s tedious, error-prone, and frankly, a bit ugly. Go’s designers, in their infinite wisdom (a phrase I use with the same sincerity as “interesting weather we’re having”), gave us a better tool: errors.As. This function is your surgical extractor for specific error types from within a chain.

20.5 errors.Is: Checking Error Identity Through Wrapped Chains

Alright, let’s talk about errors.Is. This is where Go’s error handling graduates from “well, it’s simple” to “oh, actually, that’s quite clever.” You’ve probably been there: you get an error, you unwrap it, you start doing == checks or peeking at its message like a detective at a crime scene. It’s clunky, brittle, and frankly, a bit amateur hour. The errors.Is function is your ticket out of that mess. Think of it as a bloodhound that can sniff its way through a whole chain of wrapped errors to find a specific target. It doesn’t just check the error on the surface; it recursively unwraps the entire error chain, looking for a match. This is the idiomatic, robust way to check for specific types of errors in Go.

20.4 Error Wrapping with %w and fmt.Errorf

Right, let’s talk about wrapping errors. This is where we go from the polite but utterly useless “something went wrong” to the glorious, detailed “the flux capacitor failed because you tried to input 1.21 gigawatts on a 15-amp household circuit, you maniac.” Before Go 1.13, we were all basically doing this by hand, attaching context with fmt.Errorf("some context: %v", err). It worked, but it was a string-concatenation free-for-all. There was no standard way to unwrap the error and get back to the original cause. The %w verb and the accompanying errors package changes fixed that. It’s one of those “why wasn’t it always like this?” features.

20.3 Sentinel Errors: errors.New() and Exported Error Values

Right, let’s talk about sentinel errors. This is the part where we graduate from just returning fmt.Errorf("something broke") and start building an error handling strategy that doesn’t suck. The name sounds fancy, but the concept is simple: a sentinel error is a predefined, exported (public) error value that you can check against. Think of them as unique error constants, like little flags your code can raise to signal specific, well-known problems.

20.2 Returning Errors: The (value, error) Convention

Alright, let’s talk about the single most brilliant and simultaneously most annoying piece of Go syntax you’ll encounter: (value, error). It’s the backbone of how we handle things going wrong, and it’s so ingrained in the language’s DNA that you’ll feel its absence when you go back to languages that just chuck exceptions around like confetti. The core idea is devastatingly simple: any function that can fail should return both the thing you wanted and a separate, explicit error value. If the function succeeded, you get your value and a nil error. If it failed, you get a zero-value (or whatever partial result was achieved) and a non-nil error describing what went pear-shaped. This isn’t a suggestion; it’s a convention so strong it might as well be law. The compiler won’t yell at you if you don’t do it, but every other Go programmer will.

20.1 The error Interface: Error() string

Right, let’s talk about error. It’s the one interface you’ll use more than any other, and its design is so stupidly simple it’s almost offensive. Here’s the entire definition, straight from the source: type error interface { Error() string } That’s it. No GetMessage(), no GetStatusCode(), no GetUnderlyingCause(). Just a single method that returns a string. When the Go designers landed on this, I imagine there were high-fives all around. They had achieved maximum simplicity. It’s brilliant because it’s minimal, and it’s infuriating for the exact same reason. But before we get mad, let’s understand the genius in the constraint.

— joke —

...