Skip to content

Panics and Unwrap

A panic is Rust’s way of saying “this situation is a bug — I cannot continue safely.” When a panic fires, Rust unwinds the call stack, runs destructors, and terminates the current thread. In a single-threaded program that means the whole process exits.

You can trigger a panic explicitly with the panic! macro:

fn main() {
panic!("something went terribly wrong");
}

Several standard operations also panic automatically when used incorrectly:

fn main() {
let v = vec![1, 2, 3];
println!("{}", v[10]); // index out of bounds — panic!
}
fn main() {
let x: Option<i32> = None;
let _ = x.unwrap(); // called unwrap() on a None value — panic!
}

.unwrap() on an Option<T> or Result<T, E> extracts the inner value if it is Some/Ok, and panics if it is None/Err. It is a shortcut that trades safety for brevity.

.expect(msg) does the same thing but lets you supply a descriptive message that appears in the panic output, making debugging much easier.

fn main() {
let some_value: Option<i32> = Some(42);
let n = some_value.unwrap(); // fine — value is Some
println!("n = {}", n);
let parsed: Result<i32, _> = "99".parse();
let m = parsed.expect("input should always be a valid integer");
println!("m = {}", m);
}

Panics are appropriate when:

  • The situation represents a bug in your code, not a user or environment failure.
  • You are writing tests or prototypes where crashing fast is useful.
  • You have already validated the preconditions elsewhere and the invariant truly cannot be violated (e.g., unwrap() on a value you just inserted).

Panics are not appropriate when:

  • The failure can come from external input (files, network, user data).
  • The caller might want to recover and try something else.
  • You are writing library code that other programs call.

Safer Defaults with .unwrap_or and .unwrap_or_else

Section titled “Safer Defaults with .unwrap_or and .unwrap_or_else”

When you want a fallback value instead of a panic, use .unwrap_or(default) or .unwrap_or_else(|| compute_default()).

fn safe_sqrt(n: f64) -> Result<f64, String> {
if n < 0.0 {
Err(format!("cannot take sqrt of negative number: {}", n))
} else {
Ok(n.sqrt())
}
}
fn main() {
// .unwrap_or provides a default instead of panicking
let a: Option<i32> = Some(42);
let b: Option<i32> = None;
println!("a unwrap_or: {}", a.unwrap_or(0));
println!("b unwrap_or: {}", b.unwrap_or(0));
// .expect gives a custom panic message (use only when failure is a bug)
let good = safe_sqrt(16.0).expect("sqrt should not fail on positive numbers");
println!("sqrt(16) = {}", good);
// .unwrap_or_else with a closure for recoverable defaults
let result = safe_sqrt(-4.0).unwrap_or_else(|e| {
println!("Handled: {}", e);
0.0
});
println!("result = {}", result);
}
What happens when you call .unwrap() on a None value?
What is the difference between .unwrap() and .expect(msg)?
Which situation is a legitimate use of panic!?
What does .unwrap_or(default) do when the Option is None?