Rust has no exceptions. Instead, a function that can fail returns Result<T, E>, an enum with Ok(T) for success and Err(E) for failure. Because it's an ordinary type, the compiler forces the caller to handle both cases somehow — there's no way to silently ignore a possible error the way an uncaught exception might slip past in other languages.
Result<T, E> and match
The standard library defines Result as an ordinary two-variant enum, handled the same way as any other enum — typically with match.
// enum Result<T, E> { Ok(T), Err(E) }
use std::fs::File;
let f = File::open("hello.txt");
match f {
Ok(file) => println!("opened: {:?}", file),
Err(error) => println!("failed: {:?}", error),
}The ? operator: one character for error propagation
? after a Result-returning expression unwraps Ok automatically, or returns early with the Err — converting it via From if the error types differ. This replaces what would otherwise be a repetitive match-and-early-return in every fallible function.
use std::fs::File;
use std::io::{self, Read};
fn read_username() -> Result<String, io::Error> {
let mut f = File::open("username.txt")?; // returns early on Err
let mut s = String::new();
f.read_to_string(&mut s)?; // same here
Ok(s)
}unwrap, expect, and when they're acceptable
unwrap() panics immediately on Err, extracting the Ok value or crashing; expect("message") does the same with a custom panic message. Idiomatic Rust reserves these for cases where failure genuinely means a bug (prototypes, tests, or an invariant that truly cannot fail) rather than production error handling, where ? or an explicit match is preferred.
let f = File::open("hello.txt").unwrap(); // panics if the file is missing
let f = File::open("hello.txt").expect("hello.txt should exist"); // panics with a custom message