Rust has no exceptions. Failures a caller is expected to handle are returned as values
of type Result or Option. Bugs and states the program cannot
recover from cause a panic (Reference: Panic).
Panic
A panic stops the current computation. It is raised by panic!, and by
failing assertions, out-of-bounds indexing, integer overflow with checks on, and
division by zero, among others.
| Macro | Purpose |
|---|---|
panic!("…") | Panics with a formatted message |
assert!, assert_eq!, assert_ne! | Panic if a condition fails |
debug_assert! and its variants | The same, but only when debug_assertions is on |
unreachable! | Marks code that should not be reached |
todo!, unimplemented! | Mark unfinished code |
In edition 2021 and later, panic! always treats its first argument as a
format string. std::panic::panic_any(x) panics with a value of any type.
Unwinding and aborting
A panic strategy is chosen with the -C panic compiler flag, or
panic = "abort" in a Cargo profile.
| Strategy | Effect |
|---|---|
unwind | The default on most targets. Frames are unwound and destructors run. std::thread::spawn and std::panic::catch_unwind are recovery points |
abort | The process stops at once and no destructors run. The optimiser can assume no unwinding, reducing code size |
- A crate built with
unwindcan be linked with the abort handler. A crate built withabortcannot use the unwind handler. - When a thread panics, other threads continue. A
JoinHandle::joinreturnsErr. A panic in the main thread ends the program. catch_unwindrecovers from a panic within the panicking thread; it is meant for boundaries such as FFI, not for ordinary error handling. Catching a foreign exception has unspecified behaviour.- Panics may not unwind across a non-unwinding ABI, such as
extern "C"; that is undefined behaviour if it happens from foreign code. Useextern "C-unwind"where unwinding must cross.
The panic handler
With std, the runtime provides the panic handler. A
no_std binary must define exactly one function with the
#[panic_handler] attribute and the signature
fn(&PanicInfo) -> !.
#![no_std]
use core::panic::PanicInfo;
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
Result
pub enum Result<T, E> {
Ok(T),
Err(E),
}
Result holds a success value or an error. It is marked
#[must_use], so ignoring one gives a warning.
| Method | Result |
|---|---|
unwrap(), expect("msg") | The success value, or a panic |
unwrap_or(d), unwrap_or_else(f), unwrap_or_default() | The success value, or a fallback |
is_ok(), is_err() | Test without consuming |
map(f), map_err(f) | Transform the value or the error |
and_then(f) | Chain a function that returns a Result |
ok(), err() | Convert to an Option |
as_ref(), as_mut() | Borrow the contents |
Option
pub enum Option<T> {
None,
Some(T),
}
Option represents a value that may be absent. It stands in for the null
values of other languages, and Option<&T>,
Option<Box<T>> and similar have the same size as the bare type (see
Layout).
| Method | Result |
|---|---|
unwrap(), expect("msg") | The value, or a panic |
unwrap_or(d), unwrap_or_else(f), unwrap_or_default() | The value, or a fallback |
is_some(), is_none() | Test without consuming |
map(f), and_then(f), filter(f) | Transform the value |
ok_or(e), ok_or_else(f) | Convert to a Result |
take(), replace(v) | Remove or swap the value in place |
as_ref(), as_mut() | Borrow the contents |
The ? operator
The ? operator returns the
contained value, or returns early with the failure. On a Result, the error
is converted with From::from to the function’s error type.
use std::fs;
use std::num::ParseIntError;
#[derive(Debug)]
enum ReadError {
Io(std::io::Error),
Parse(ParseIntError),
}
impl From<std::io::Error> for ReadError {
fn from(e: std::io::Error) -> Self { ReadError::Io(e) }
}
impl From<ParseIntError> for ReadError {
fn from(e: ParseIntError) -> Self { ReadError::Parse(e) }
}
fn read_number(path: &str) -> Result<i32, ReadError> {
let text = fs::read_to_string(path)?;
let n = text.trim().parse::<i32>()?;
Ok(n)
}
FromThe Error trait
std::error::Error (also in core::error) is the common trait
for error values. It requires Debug and Display, and has a
source method for the underlying cause.
Box<dyn Error>, often with + Send + Sync, is the usual
type for “any error”, and ? converts into it automatically.
use std::error::Error;
use std::fmt;
#[derive(Debug)]
struct BadInput;
impl fmt::Display for BadInput {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "bad input")
}
}
impl Error for BadInput {}
fn check(ok: bool) -> Result<(), Box<dyn Error>> {
if !ok { return Err(Box::new(BadInput)); }
Ok(())
}
main and exit codes
main may return any type implementing Termination, such as
Result<(), E> with E: Debug. An Err prints
the error and exits with a failure status. std::process::exit(code) ends
the process without running destructors.
fn main() -> Result<(), Box<dyn std::error::Error>> {
let n: i32 = "42".parse()?;
println!("{n}");
Ok(())
}