BudiBadu Logo
Samplebadu

Rust by Example: Error Handling with Result

Rust 1.75+

Handle runtime errors robustly using Rust's Result type. Learn the difference between recoverable and unrecoverable errors, how to pattern match on Results, and how to use the '?' operator for concise error propagation.

Code

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

fn main() {
    // --- Basic Result Handling ---
    let f = File::open("hello.txt");
    
    let f = match f {
        Ok(file) => file,
        Err(error) => {
            println!("Problem opening the file: {:?}", error);
            return; // Exit main
        },
    };
    
    println!("File opened successfully: {:?}", f);
    
    // --- Propagating Errors ---
    match read_username_from_file() {
        Ok(username) => println!("Username: {}", username),
        Err(e) => println!("Error reading username: {}", e),
    }
}

// Function that returns a Result
fn read_username_from_file() -> Result<String, io::Error> {
    // The '?' operator is a shortcut for propagating errors
    // If File::open fails, '?' returns the Err immediately.
    // If it succeeds, it unwraps the Ok value into 'f'.
    let mut f = File::open("username.txt")?;
    
    let mut s = String::new();
    
    // Same here: if read_to_string fails, return Err.
    f.read_to_string(&mut s)?;
    
    // If we got here, everything is fine. Return Ok.
    Ok(s)
    
    // The above can be shortened to:
    // File::open("username.txt")?.read_to_string(&mut s)?;
    // Ok(s)
}

Explanation

Rust groups errors into two major categories: recoverable and unrecoverable errors. For a recoverable error, such as a file not found, we want to report the problem to the user and retry the operation. Unrecoverable errors are symptoms of bugs, like trying to access a location beyond the end of an array.

Most languages use exceptions for error handling. Rust does not have exceptions. Instead, it uses the type Result<T, E> for recoverable errors. Result is simply an enum:

  • Ok(T): Represents success and contains the value.
  • Err(E): Represents failure and contains the error.

The ? operator is a convenient shorthand for propagating errors. When placed after a Result value, it works like this: if the value is Ok, the value inside is returned from the expression and the program continues. If the value is Err, the Err is returned from the whole function immediately, passing the error up to the caller. It effectively desugars to a match statement that returns early on error.

Code Breakdown

8
match f. This is the manual way to handle errors. We explicitly check if File::open returned Ok or Err. This forces the programmer to decide what to do in failure cases.
30
File::open("username.txt")?. The ? operator replaces the verbose match statement. It says "unwrap the value if successful, or return the error to the caller if failed". It makes error handling code linear and readable.
26
-> Result. The return type of the function. It promises to return either a String (on success) or an io::Error (on failure).