Rust errors are divided into two types. Errors that the caller can handle meaningfully, recoverable errors, are represented by Result<T, E>, while situations where "something went wrong — do not proceed further," unrecoverable errors, are handled with panic!. In this chapter, we summarize the operation and debugging tips of panic!, index OOB, and unwrap/expect.
Learning Objectives
- Understand the meaning and termination method of the
panic!macro. - Know common causes of panics such as index OOB, integer overflow,
unwrap, etc. - Develop the habit of increasing debugging possibilities using
expect("Reason")messages. - Check the stack trace with
RUST_BACKTRACE=1.
Key Concepts
1) Operation of panic!
When panic!("Message") is called, the basic operation is as follows:
- Print the error message and location to the console.
- Unwind the current thread's stack and drop all local variables
drop. - If the thread is the main thread, the process terminates (exit code ≠ 0).
If panic = "abort" is used in [profile] of Cargo.toml, it terminates immediately without unwinding, reducing the binary size.
2) Common causes of panic
| Cause | Example | Safe alternative |
|---|---|---|
| Index OOB | v[10] but length 3 |
v.get(10) (Option) |
| Integer overflow (debug) | u8 + u8 |
checked_add, wrapping_add |
unwrap/expect |
None.unwrap() |
match/?/unwrap_or |
| Division by 0 (integer) | n / 0 |
Pre-check or checked_div |
3) unwrap vs expect
let n: i32 = "42".parse().unwrap(); //General message on failure
let n: i32 = "42".parse().expect("Input must always be an integer"); //Our message on failure
It is easier for the next person to understand if you write the message for expect as "Why must this not fail here."
4) Viewing Backtrace
RUST_BACKTRACE=1 cargo run --bin ex01_explicit
RUST_BACKTRACE=full cargo run --bin ex01_explicit
You can see the entire call stack, which is very helpful for debugging.
5) Recoverable vs Non-recoverable — How to Choose?
| Situation | Handling |
|---|---|
| User input · external data · I/O | Result + ? (recoverable) |
| Internal library integrity check (invariant violation) | panic! |
| Obviously unreachable branch | unreachable!() |
| Branch not yet implemented | todo!() |
Library code almost always returns Result as a polite manner. Leave the decision to the caller.
Examples
These examples catch panic with
catch_unwindfor learning purposes to produce clean output. In real code,catch_unwindis rarely used — usually just let it die.
Example 1 — ex01_explicit.rs: calling panic! directly
//Episode 17 Example 1: Direct call to panic!
//
//When you encounter an "irrecoverable" situation, use panic!("message") to immediately terminate the program.
//This example wraps it in catch_unwind to show both normal flow and panic flow.
//(In real code, catch_unwind is rarely used — usually you just let it die.)
fn divide(a: i32, b: i32) -> i32 {
if b == 0 {
panic!("Cannot divide by zero (a={a})");
}
a / b
}
fn main() {
//Turning off the default panic hook makes the stack trace invisible and keeps the console clean.
std::panic::set_hook(Box::new(|_| {}));
println!("Normal: 10/2 = {}", divide(10, 2));
//Catch a panic and extract the message (for learning purposes)
let result = std::panic::catch_unwind(|| divide(10, 0));
if let Err(payload) = result {
let msg = payload
.downcast_ref::<String>()
.cloned()
.or_else(|| payload.downcast_ref::<&str>().map(|s| s.to_string()))
.unwrap_or_else(|| String::from("(Failed to extract message)"));
println!("Panic occurred! Message: {msg}");
}
}
Example 2 — ex02_index_oob.rs: Index OOB
//Example 17-2: Index Out of Bounds Panic
//
//Accessing an index out of bounds in arrays or vectors is not caught by the compiler and panics at runtime.
//A safe alternative is `.get(i)` — it returns the result as an Option.
fn main() {
std::panic::set_hook(Box::new(|_| {}));
let v = vec![10, 20, 30];
println!("v[0] = {}, v[2] = {}", v[0], v[2]);
//v[10] is an OOB index → panic
let r = std::panic::catch_unwind(|| v.clone()[10]);
if r.is_err() {
println!("Accessing v[10] causes a panic — because it is out of bounds.");
}
//Safe alternative: receive as Option
println!("v.get(2) = {:?}", v.get(2));
println!("v.get(10) = {:?}", v.get(10));
}
Example 3 — ex03_expect.rs : expect message
//Example 3 of Lesson 17: expect and RUST_BACKTRACE
//
//`unwrap` only displays the general message "panicked if there is no value."
//`expect("reason")` prints a message along with the panic, which is very helpful for debugging.
//Also, if you run with the environment variable RUST_BACKTRACE=1, you can see the call stack.
fn main() {
std::panic::set_hook(Box::new(|_| {}));
//Using expect in a safe place — the message becomes documentation of "why it is safe."
let s = "42";
let n: i32 = s.parse().expect("This place is always an integer — verified above");
println!("n = {n}");
//Check the expect message on failure
let r = std::panic::catch_unwind(|| {
let _: i32 = "abc".parse().expect("Input must always be an integer");
});
if r.is_err() {
println!("expect panic occurs — message is very helpful for debugging.");
}
println!("If you run with the environment variable RUST_BACKTRACE=1, you can check the stack trace.");
}
Common Mistakes
Q. Can a panic be caught with catch?
A. It can be caught with std::panic::catch_unwind, but this is not a general exception handling mechanism. It is meant for special purposes, such as preventing FFI boundaries or background tasks from killing the main thread. For regular code, use Result and ?.
Q. If panic! occurs, do other threads also die?
A. Only the thread where the panic occurs dies. If the main thread dies, the entire process ends. This will be revisited in the thread lesson in Part 21.
Q. Is integer overflow always a panic?
A. It panics in debug builds and wraps in release builds. If wrapping is intended, use wrapping_add; if detection is needed, explicitly use checked_add (Option) / overflowing_add (tuple).
Q. Is it okay to use panic in a library?
A. It is allowed when the caller is in an unrecoverable situation (such as invariant violation), but panicking for user input or external data is very rude. Usually, the library returns information via Result and lets the user decide.
Summary
panic!is the last resort for unrecoverable situations — thread termination + drop.- Common causes include index OOB, integer overflow (debug), and
unwrap. - Leaving a message with
expect("Reason")instead ofunwrapmakes debugging easier. - The library almost always returns
Result— the decision is up to the caller.
Try it yourself
Solve the assignment below. The reference solution is shown below.