Course contentsShow
← Rust
Lesson 858 of 1,9107. Error Handling PatternsFree lesson

Error Handling in Multi-Threaded Contexts

Propagate failures from worker threads to main execution threads safely.

In this lesson, you will learn how to transport errors from independent worker threads back to the main thread so you can handle them centrally.

When you spawn a thread using std::thread::spawn, the code inside that thread doesn't have a direct way to return an Result to the main function because the spawn signature expects a closure that returns the thread's value. If a task fails, simply letting the thread end silently or panic isn't enough; we need a reliable way to catch that failure.

The JoinHandle Pattern

When you spawn a thread, it returns a JoinHandle<T>. When you call .join() on this handle, it returns a Result<T, Box<dyn Any + Send>>.

If your thread logic returns a Result<T, MyError>, the JoinHandle will return a Result<Result<T, MyError>, Box<dyn Any>>. The outer Result handles the case where the thread panicked, while the inner Result contains the actual domain error you generated.

Here is how you safely propagate that error:

use std::thread;

fn main() {
    let handle = thread::spawn(|| -> Result<i32, String> {
        // Imagine some complex operation here
        Err("Something went wrong in the worker!".to_string())
    });

    // We join the thread and handle the two-tier error structure
    match handle.join() {
        Ok(Ok(value)) => println!("Success: {}", value),
        Ok(Err(e)) => println!("Worker failed with error: {}", e),
        Err(_) => println!("Worker thread panicked!"),
    }
}

Why this matters

By wrapping our result in a Result inside the thread, we distinguish between a "logic failure" (the operation failed) and a "runtime failure" (the thread crashed/panicked). Because Result implements the Send trait, it can be safely passed across thread boundaries as long as the error type itself is also Send.

This pattern allows you to treat worker threads as fallible components of your system, ensuring that an error in a background task doesn't vanish, but instead becomes a value that the main thread can inspect, log, or recover from.

Key Takeaway: Propagate errors from worker threads by returning a Result from the thread closure, then unpack the JoinHandle to handle both potential panics and expected domain errors.