Error Handling in Rust: try/catch and Beyond — Advanced Level

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Error Handling in Rust: try/catch and Beyond — Advanced Level

\n\n

Unlike many languages where exceptions are the standard mechanism (like try/catch in Python, Java, or C++), Rust offers a fundamentally different approach. There are no exceptions in the classical sense. Instead, the language uses a powerful type system to represent and handle errors. For a beginner, this may seem complex, but at an advanced level, you will appreciate the safety and predictability this approach provides. In this article, we will explore how to work with errors in Rust professionally: from basic Result and Option to advanced crates like anyhow and thiserror, and draw parallels with the familiar try/catch.

\n\n

1. Foundation: Result, Option, and the ? Operator

\n\n

The foundation of error handling in Rust is the Result<T, E> enum. It can be either Ok(T) (success) or Err(E) (error). An analog of try/catch can be considered the ? operator. It simplifies propagating the error upward: if the value is Err, the function immediately returns it; if Ok, it extracts the value.

\n\n
use std::fs::File;\nuse std::io::Read;\n\nfn read_file(path: &str) -> Result<String, std::io::Error> {\n    let mut file = File::open(path)?; // Analog of try: propagate error\n    let mut content = String::new();\n    file.read_to_string(&mut content)?;\n    Ok(content)\n}
\n\n

In this example, File::open and read_to_string can return Err. The ? operator replaces a verbose match and makes the code clean, like try/catch in other languages. However, unlike exceptions, you explicitly specify the error type in the function signature — this improves readability and safety.

\n\n

The Option<T> enum is used for cases where a value may be absent (analogous to None in Python). The ? operator also works with it:

\n\n
fn get_first_element(vec: &Vec<i32>) -> Option<&i32> {\n    vec.first()? // If the vector is empty, return None\n}
\n\n

2. Advanced Work with Error Types: From and Error Traits

\n\n

When a function calls multiple operations with different error types (e.g., I/O and parsing), a problem arises: how to return a single type? The solution is the From trait and a custom error type.

\n\n

Let's create our own error structure and implement the std::error::Error trait for it (automatically via #[derive(Debug)] and manual implementation of Display):

\n\n
use std::fmt;\nuse std::io;\n\n#[derive(Debug)]\nenum MyError {\n    Io(io::Error),\n    Parse(std::num::ParseIntError),\n}\n\nimpl fmt::Display for MyError {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        match self {\n            MyError::Io(e) => write!(f, "IO error: {}", e),\n            MyError::Parse(e) => write!(f, "Parse error: {}", e),\n        }\n    }\n}\n\nimpl std::error::Error for MyError {}\n\n// Implement From for automatic conversion\nimpl From<io::Error> for MyError {\n    fn from(error: io::Error) -> Self {\n        MyError::Io(error)\n    }\n}\n\nimpl From<std::num::ParseIntError> for MyError {\n    fn from(error: std::num::ParseIntError) -> Self {\n        MyError::Parse(error)\n    }\n}\n\nfn process_data(path: &str) -> Result<i32, MyError> {\n    let content = std::fs::read_to_string(path)?; // Automatically converts to MyError::Io\n    let number: i32 = content.trim().parse()?;    // Automatically converts to MyError::Parse\n    Ok(number)\n}
\n\n

Now the process_data function returns a single type MyError, and the ? operator automatically converts errors thanks to the implemented trait

Blogs

Book Recommendations