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 thetry!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