Skip to main content
Rust Programming
CHAPTER 15 Beginner

Error Handling in Rust

Updated: May 18, 2026
5 min read

# CHAPTER 15

Error Handling in Rust

1. Chapter Introduction

In most languages, when something goes wrong (like a missing file or a database failure), the language throws an "Exception". If you forget to wrap it in a try/catch block, your server crashes. Rust does not have Exceptions. Instead, Rust forces you to handle errors explicitly using the Type System. In this chapter, we will learn about the two types of errors in Rust: Unrecoverable errors (panic!) and Recoverable errors (the Result Enum).

2. Learning Objectives

By the end of this chapter, you will be able to:
  • Understand when a program should panic!.
  • Understand the Result<T, E> enum for recoverable errors.
  • Extract successful data or error messages using match.
  • Use unwrap() and expect() responsibly.
  • Propagate errors up the call stack gracefully using the ? operator.

3. Unrecoverable Errors (panic!)

Sometimes, a bug is so severe that the program is in an invalid state and must be stopped immediately (e.g., trying to access an array out of bounds). In Rust, this triggers a Panic.

You can trigger a panic manually using the panic! macro:

rust
123
fn main() {
    panic!("CRITICAL FAILURE: Database connection lost!");
}

When a panic occurs, Rust cleans up memory, prints the error message, and shuts down the application.

4. Recoverable Errors (Result Enum)

Most errors are not catastrophic. If a user tries to open a file that doesn't exist, we don't want to crash the server; we want to create the file or show a warning. For this, Rust uses the Result Enum.
rust
12345
// The Result enum is built into the standard library like this:
enum Result<T, E> {
    Ok(T),  // Contains the success value
    Err(E), // Contains the error value
}

Let's attempt to open a file. The File::open function returns a Result.

rust
1234567891011
use std::fs::File;

fn main() {
    let f_result = File::open("hello.txt");

    // We MUST handle both the Ok and Err variants!
    let f = match f_result {
        Ok(file) => file, // Return the file handle
        Err(error) => panic!("Problem opening the file: {:?}", error), // Crash if missing
    };
}

5. Shortcuts: unwrap() and expect()

Writing a match statement every time is tedious. Rust provides shortcut methods on the Result type.
  • unwrap(): If the result is Ok, it returns the value inside. If the result is Err, it calls panic! automatically for you.
  • expect("msg"): Exactly like unwrap, but lets you provide a custom panic message.
rust
123456
use std::fs::File;

fn main() {
    // If hello.txt doesn't exist, this line instantly crashes the program.
    let f = File::open("hello.txt").expect("Failed to open hello.txt");
}

*Rule of thumb: Only use unwrap or expect during prototyping, or when you are 100% mathematically certain the operation will not fail.*

6. Error Propagation (The ? Operator)

If you are writing a function that opens a file, reads it, and returns the text, you probably don't want to handle the error *inside* the function. You want to pass the error back to the caller so they can decide what to do. This is called Propagating the error.

Instead of writing massive match statements to return errors early, Rust provides the glorious ? operator.

rust
1234567891011121314151617
use std::fs::File;
use std::io::{self, Read};

// Notice the return type! It returns a Result containing either a String, or an io::Error.
fn read_username_from_file() -> Result<String, io::Error> {
    // If File::open fails, the '?' immediately stops this function and RETURNS the Err to the caller!
    // If it succeeds, the file handle is assigned to 'f'.
    let mut f = File::open("username.txt")?;
    
    let mut s = String::new();
    
    // If reading fails, '?' returns the error. If success, it proceeds.
    f.read_to_string(&mut s)?;
    
    // Return the successful string wrapped in Ok()
    Ok(s)
}

The ? operator eliminates almost all boilerplate error checking code!

7. Mini Project: Safe File Reader

rust
12345678910111213
use std::fs;

fn main() {
    // fs::read_to_string does opening and reading in one step
    match fs::read_to_string("config.json") {
        Ok(contents) => println!("Config loaded: {}", contents),
        Err(e) => {
            println!("Warning: Could not read config file.");
            println!("System Reason: {}", e);
            println!("Falling back to default settings...");
        }
    }
}

8. Common Mistakes

  • Using ? inside main(): By default, main() returns an empty tuple (). The ? operator requires the function to return a Result. If you use ? in main(), it won't compile unless you change the signature of main to fn main() -> Result<(), Box<dyn std::error::Error>>.
  • Overusing unwrap(): In a production server, using unwrap() is dangerous because any missing file or bad network connection will crash the entire server instance.

9. Best Practices

  • Embrace ?: Use the ? operator extensively to keep your functions clean and push error handling to the top level of your architecture.

10. Exercises

  1. 1. Write a function divide(a: f64, b: f64) -> Result<f64, String>.
  1. 2. If b is 0.0, return Err(String::from("Cannot divide by zero")). Otherwise, return Ok(a / b).
  1. 3. In main, call the function inside a match statement and print either the result or the error string.

11. MCQs with Answers

Q1. Does Rust use try/catch Exceptions? a) Yes b) No, it uses the Type System (Result Enum) to handle errors Answer: b) No, it uses the Type System.

Question 2

What macro is used to deliberately crash a program in an unrecoverable state?

Question 3

What are the two variants of the Result<T, E> Enum?

Question 4

What does the .unwrap() method do on a Result?

Question 5

How is .expect("msg") different from .unwrap()?

Question 6

What does the ? operator do when placed after a function that returns a Result?

Q7. Can you use the ? operator in a function that does NOT return a Result or Option? a) Yes b) No, the compiler will throw an error Answer: b) No, the compiler will throw an error.
Question 8

Why is Rust's approach to error handling considered safer than Exceptions?

Question 9

If File::open fails, what variant of the Result enum does it return?

Question 10

In a high-traffic production web server, what method should you almost NEVER use when processing user data?

12. Interview Questions

  • Q: Contrast the Option enum with the Result enum. When do you use each? (Answer: Option is for absence of data, Result is for the presence of an error).
  • Q: Explain how the ? operator reduces boilerplate code in error propagation.

13. Summary

Rust eliminates the uncertainty of Exceptions by forcing errors to be part of a function's return signature. By utilizing the Result enum, developers are compelled to acknowledge and handle potential failures. The brilliant ? operator ensures that this strictness does not result in bloated, unreadable code.

14. Next Chapter Recommendation

Our applications are getting large, and keeping everything in main.rs is bad practice. In Chapter 16: Modules and Packages, we will learn how to split our code across multiple files, manage privacy (Public vs Private), and utilize the Cargo package manager to import third-party libraries.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·