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.
Triggering a panic manually
Section titled “Triggering a panic manually”fn main() { panic!("something went badly wrong");}thread 'main' (12345) panicked at src/main.rs:2:5:something went badly wrongnote: run with `RUST_BACKTRACE=1` environment variable to display a backtraceThe number in parentheses, (12345) here, is the thread’s ID. You’ll see a different number on every run, and older Rust versions leave it out.
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));}fn main() { let value: Option<i32> = None; println!("{}", value.unwrap()); // panics: called `Option::unwrap()` on a `None` value}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));}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.
panic! vs Result
Section titled “panic! vs Result”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 Resultfn 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 — panicfn 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}");}If parsing fails, the panic message includes your text, which makes debugging a lot easier than the generic message .unwrap() gives you.
Checking your assumptions: assert!, assert_eq! and unreachable!
Section titled “Checking your assumptions: assert!, assert_eq! and unreachable!”Sometimes you know something must be true at a certain point in your code. Rust has a few small macros that turn that belief into a check. If the check fails, they panic with a clear message, so a bug shows up right where it starts instead of causing strange results later.
assert!(condition, "message")panics if the condition isfalse.assert_eq!(left, right)panics if the two values are not equal, and prints both of them.unreachable!("message")marks code that should never run, like amatcharm for a value that can’t happen.
fn average(numbers: &[i32]) -> i32 { assert!(!numbers.is_empty(), "average needs at least one number");
let mut total = 0; for n in numbers { total += n; } total / numbers.len() as i32}
fn day_name(day: u8) -> &'static str { match day { 1 => "Monday", 2 => "Tuesday", 3 => "Wednesday", 4 => "Thursday", 5 => "Friday", 6 => "Saturday", 7 => "Sunday", _ => unreachable!("day must be 1 to 7, got {day}"), }}
fn main() { let avg = average(&[70, 80, 90]); assert_eq!(avg, 80); println!("average: {avg}");
println!("day 3 is {}", day_name(3));}Output:
average: 80day 3 is WednesdayEvery check passes here, so the program runs normally. Now change the list to [70, 80, 95] in the playground. The average becomes 81, and assert_eq! stops the program with both values side by side:
thread 'main' (12345) panicked at src/main.rs:26:5:assertion `left == right` failed left: 81 right: 80Try average(&[]) or day_name(9) too, and you’ll see your own messages in the panic.
These checks always run, even in a release build, so keep them cheap. You’ll meet assert_eq! again on the Testing page, where it’s the main way a test says “this should equal that”. There is also todo!(), which panics with “not yet implemented”. It’s handy as a placeholder body while you sketch out a function that you’ll finish later.
Practice
Section titled “Practice”1. A port number with expect
Section titled “1. A port number with expect”You have a config value let port_text = "8080";. Turn it into a u16 with .parse() and .expect(...), using a message that explains what went wrong. Then print Listening on port 8080. Hint: the variable needs a type, let port: u16 = ..., so parse knows what to produce. Afterwards, change the text to "80a0" to see your message in the panic.
Show solution
fn main() { let port_text = "8080"; let port: u16 = port_text .parse() .expect("the port in the config should be a number from 0 to 65535"); println!("Listening on port {port}");}Output:
Listening on port 80802. Split the bill
Section titled “2. Split the bill”Write fn split_bill(total: u32, people: u32) -> u32 that returns each person’s share. Splitting between zero people is a bug in the calling code, so use assert! with a clear message to catch it. Print the share for a total of 120 and 4 people. Hint: put the assert! on the first line of the function, before the division.
Show solution
fn split_bill(total: u32, people: u32) -> u32 { assert!(people > 0, "split_bill needs at least one person"); total / people}
fn main() { let share = split_bill(120, 4); println!("Each person pays {share}");}Output:
Each person pays 30panic!stops the program immediately with an error message.- Some standard operations (bad array index,
.unwrap()onNone, division by zero) panic automatically. - Use
panic!for bugs/invariants that should be impossible; useResultfor failures a caller can reasonably handle. .expect("message")is like.unwrap(), but with a message that makes the panic easier to debug.assert!,assert_eq!andunreachable!panic on purpose when something you believed to be true turns out false.
Quick check
Section titled “Quick check”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
Read more
Section titled “Read more”- Unrecoverable Errors with panic! — The Rust Book: what happens during a panic, unwinding, and how to read a backtrace.
- To panic! or Not to panic! — The Rust Book: the official guidelines for choosing between
panic!andResult, with examples. - panic! — Rust by Example: a short example of a panic, showing that memory is still freed as the program unwinds.
assert_eq!— std docs: how the assertion macros work, including custom messages.panic!— std docs: the reference forpanic!itself, how it relates tounwrap, and when a panic is the right tool.