Skip to content

Rust Panic

A panic is Rust’s way of stopping a program immediately when something has gone unrecoverably wrong. You’ve actually already caused a few without meaning to, if you’ve followed along with earlier pages — indexing past the end of an array, or calling .unwrap() on a None, both panic.

fn main() {
panic!("something went badly wrong");
}
thread 'main' panicked at src/main.rs:2:5:
something went badly wrong
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
▶ Try it Yourself

When this runs, the program prints that message and stops — nothing after the panic! executes.

Things that panic without you calling panic! directly

Section titled “Things that panic without you calling panic! directly”

A few operations you’ve already met will panic on their own if something’s wrong. Each example below reads its risky value through a function parameter rather than a hardcoded literal — with a literal index or a literal 0 right there in the code, the compiler can often work out at compile time that the operation always fails, and refuses to build at all instead of letting it run and panic.

fn get(numbers: &[i32], index: usize) -> i32 {
numbers[index] // panics: index out of bounds
}
fn main() {
let numbers = [1, 2, 3];
println!("{}", get(&numbers, 10));
}
▶ Try it Yourself
fn main() {
let value: Option<i32> = None;
println!("{}", value.unwrap()); // panics: called `Option::unwrap()` on a `None` value
}
▶ Try it Yourself
fn divide(a: i32, b: i32) -> i32 {
a / b // panics: attempt to divide by zero
}
fn main() {
let a = 10;
let b = 0;
println!("{}", divide(a, b));
}
▶ Try it Yourself

Each of these is Rust refusing to let your program continue in a state it can’t make sense of, rather than quietly returning garbage.

This is the real decision you’ll make often: should this failure crash the program, or should the caller get a chance to handle it?

  • panic! — for bugs and situations that should never happen if the code is correct. There’s no sensible way to recover, so stopping immediately (before doing more damage) is the safest option.
  • Result (covered on the previous page) — for failures a caller might reasonably expect and want to handle, like bad user input, a missing file, or a failed network request.
// Bad input from a user — recoverable, use Result
fn parse_age(input: &str) -> Result<u32, String> {
input.parse().map_err(|_| String::from("not a valid age"))
}
// An invariant that should be impossible if the code is correct — panic
fn get_first(numbers: &[i32]) -> i32 {
if numbers.is_empty() {
panic!("get_first called with an empty slice");
}
numbers[0]
}

unwrap() and expect() are shortcuts to panicking

Section titled “unwrap() and expect() are shortcuts to panicking”

You’ve seen .unwrap() on both Option and Result — it’s really just a shortcut for “give me the value, or panic if there isn’t one.” .expect("message") does the same thing but lets you attach a more useful message:

fn main() {
let input = "42";
let number: i32 = input.parse().expect("input should be a valid number");
println!("{number}");
}
▶ Try it Yourself

If parsing fails, the panic message includes your text, which makes debugging a lot easier than the generic message .unwrap() gives you.

  • panic! stops the program immediately with an error message.
  • Some standard operations (bad array index, .unwrap() on None, division by zero) panic automatically.
  • Use panic! for bugs/invariants that should be impossible; use Result for failures a caller can reasonably handle.
  • .expect("message") is like .unwrap(), but with a message that makes the panic easier to debug.

Quick check

1. What does calling panic! do?

2. According to the rule of thumb on this page, when should you use panic! instead of Result?

3. What's the difference between .unwrap() and .expect('message')?

Score: 0 / 3