Error Handling in Rust: try/catch and Beyond — A Complete Guide

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

Introduction: Why doesn't Rust use try/catch?



In most programming languages (JavaScript, Python, Java) error handling is built on the exception mechanism and the try/catch construct. However, Rust lacks this approach. Instead, developers use a powerful type system that makes errors part of the function signature. This is not an accident, but a deliberate architectural decision: Rust strives for predictability and safety at the compilation stage, eliminating hidden program crashes.



In this article, we will cover:

  • Why Rust abandoned try/catch;
  • How the main tools work: Option, Result, unwrap(), expect();
  • What the ? operator (question mark) is;
  • When to use panic! and the try! macro (deprecated);
  • Modern error handling practices in Rust 2024.


1. Basics: Option and Result — two main types



The Option type: when a value may be absent



Option<T> is an enum that represents either a value (Some(T)) or its absence (None). It is ideal for situations where an error in the classical sense does not occur, but the result may be empty.



fn find_user(id: u32) -> Option<String> {    let database = vec!["Alice".to_string(), "Bob".to_string()];    if id < database.len() {        Some(database[id].clone())    } else {        None    }}

fn main() { match find_user(1) { Some(name) => println!("Found: {}", name), None => println!("User not found"), }}


The Result type: success or error



Result<T, E> is the main tool for error handling. It is also an enum: Ok(T) (success) or Err(E) (error).



use std::fs::File;use std::io::Read;

fn read_file(path: &str) -> Result<String, std::io::Error> { let mut file = File::open(path)?; let mut contents = String::new(); file.read_to_string(&mut contents)?; Ok(contents)}


2. The ? operator (question mark) — replacement for try/catch



The ? operator is the primary way to propagate errors upwards in Rust. It replaces cumbersome match constructs and makes code clean. If the result is Ok, the value is extracted and assigned to a variable. If Err, the function immediately returns that error.



use std::fs;use std::io;

fn copy_file(source: &str, destination: &str) -> io::Result<()> { let data = fs::read(source)?; // if error — exit fs::write(destination, &data)?; // if error — exit Ok(())}

fn main() -> io::Result<()> { copy_file("source.txt", "dest.txt")?; println!("File copied successfully"); Ok(())}


Important: the ? operator only works in functions that return Result, Option, or types implementing the FromResidual trait.



3. panic!, unwrap and expect — when an error is fatal



The panic! macro



panic!() causes an abnormal program termination (stack unwinding). It is used only in situations where continuing execution is impossible or meaningless: logic errors in the program, invariant violations, tests.



fn divide(a: i32, b: i32) -> i32 {    if b == 0 {        panic!("Division by zero!");    }    a / b}


The unwrap() and expect() methods



unwrap() is a quick way to get a value from Option or Result. If it contains None or Err — it will

Blogs

Book Recommendations