Web Analytics

Panic!

Intermediate ~30 min read

In Rust, a panic is an unrecoverable error that causes the program to stop execution immediately. Panics are used for situations where the program cannot continue and there's no way to recover. Understanding when and how panics occur is crucial for writing robust Rust programs.

What is a Panic?

A panic occurs when your program encounters an error that it cannot handle. When a panic happens, Rust will:

  • Print an error message
  • Unwind the stack (clean up resources)
  • Exit the program
Unrecoverable: Panics are for unrecoverable errors. If you can handle an error gracefully, use Result<T, E> instead. Panics should be rare in production code.

Using the panic! Macro

You can explicitly cause a panic using the panic! macro:

panic!("crash and burn");

When this code runs, the program will stop and print:

thread 'main' panicked at 'crash and burn', src/main.rs:2:5
note: run with `RUST_BACKTRACE=1` for a backtrace
Output
Click Run to execute your code

When Panics Occur

Panics can occur in several situations:

1. Explicit panic! Calls

if value < 0 {
    panic!("Value cannot be negative");
}

2. Index Out of Bounds

let v = vec![1, 2, 3];
let element = v[10];  // Panics! Index 10 doesn't exist
Safe Alternative: Use v.get(10) instead, which returns Option<&T> and won't panic.

3. Division by Zero

let result = 10 / 0;  // Panics!

4. Calling unwrap() on None or Err

let none: Option<i32> = None;
let value = none.unwrap();  // Panics!

unwrap() and expect()

Two common methods that can cause panics are unwrap() and expect():

unwrap()

let some_value = Some(5);
let value = some_value.unwrap();  // Returns 5

let none_value: Option<i32> = None;
let value = none_value.unwrap();  // Panics!
Avoid unwrap() in Production: unwrap() should only be used in examples, tests, or when you're absolutely certain the value exists. In production code, handle errors properly with match or Result.

expect()

expect() is like unwrap(), but allows you to provide a custom error message:

let result: Result<i32, &str> = Ok(42);
let value = result.expect("Failed to get value");  // Returns 42

let error: Result<i32, &str> = Err("something went wrong");
let value = error.expect("Failed to get value");  // Panics with message
Better Error Messages: expect() is slightly better than unwrap() because it provides context about what went wrong, making debugging easier.

Safe Alternatives to unwrap()

Instead of using unwrap(), use these safer alternatives:

1. Using match

let maybe_value: Option<i32> = Some(10);

match maybe_value {
    Some(v) => println!("Value: {}", v),
    None => println!("No value"),
}

2. Using if let

if let Some(v) = maybe_value {
    println!("Value: {}", v);
}

3. Using unwrap_or()

let value = maybe_value.unwrap_or(0);  // Returns 10 or 0 if None

4. Using unwrap_or_else()

let value = maybe_value.unwrap_or_else(|| {
    // Compute default value
    0
});

Backtraces

When a panic occurs, Rust can print a backtrace, which shows the call stack leading to the panic. This is extremely useful for debugging.

To see a backtrace, run your program with:

RUST_BACKTRACE=1 cargo run

Or set it permanently:

export RUST_BACKTRACE=1  # On Unix/Linux/Mac
set RUST_BACKTRACE=1      # On Windows
Output
Click Run to execute your code
Debugging Tip: Backtraces show the exact line where the panic occurred and the call chain that led to it. This makes it much easier to find and fix bugs.

When to Use Panic vs Result

Use panic! when:

  • Unrecoverable errors: When there's no way to recover from the error
  • Programming errors: When the error indicates a bug in your code (e.g., index out of bounds)
  • Examples and tests: When writing example code or tests where panics are acceptable
  • Prototyping: During early development when you want to fail fast

Use Result<T, E> when:

  • Recoverable errors: When the caller can handle the error
  • I/O operations: File operations, network requests, etc.
  • User input: Parsing, validation, etc.
  • Production code: When you need robust error handling
Rule of Thumb: If it's a programming error (bug), use panic. If it's an expected error condition that can be handled, use Result.

Best Practices

  • Avoid unwrap() in production: Always handle errors properly
  • Use expect() for better messages: If you must panic, use expect() with a descriptive message
  • Prefer Result for recoverable errors: Use Result when the caller can handle the error
  • Use safe alternatives: Use get(), unwrap_or(), or match instead of unwrap()
  • Enable backtraces in development: Set RUST_BACKTRACE=1 for debugging
  • Document panic conditions: If your function can panic, document when it happens

Common Mistakes

1. Using unwrap() everywhere

// Wrong - Can panic at runtime
let value = some_option.unwrap();
let result = some_result.unwrap();

// Correct - Handle errors properly
match some_option {
    Some(v) => println!("{}", v),
    None => println!("No value"),
}

match some_result {
    Ok(v) => println!("{}", v),
    Err(e) => println!("Error: {}", e),
}

2. Using index syntax without checking bounds

// Wrong - Panics if index doesn't exist
let v = vec![1, 2, 3];
let element = v[10];  // Panic!

// Correct - Use get() for safe access
match v.get(10) {
    Some(element) => println!("{}", element),
    None => println!("Index out of bounds"),
}

3. Panicking on recoverable errors

// Wrong - Should use Result
fn divide(a: i32, b: i32) -> i32 {
    if b == 0 {
        panic!("Division by zero");  // Bad!
    }
    a / b
}

// Correct - Return Result
fn divide(a: i32, b: i32) -> Result<i32, String> {
    if b == 0 {
        Err(String::from("Division by zero"))
    } else {
        Ok(a / b)
    }
}

4. Not enabling backtraces during development

// Always set RUST_BACKTRACE=1 when debugging
// export RUST_BACKTRACE=1  (Unix/Linux/Mac)
// set RUST_BACKTRACE=1     (Windows)

// This helps you see exactly where panics occur

Exercise: Panic Handling Practice

Task: Implement safe functions that handle errors without panicking.

Requirements:

  • Write a safe division function using Option
  • Write a safe vector access function
  • Write a username validation function using Result
  • Avoid using unwrap() or expect()
Output
Click Run to execute your code
Show Solution
fn safe_divide(a: i32, b: i32) -> Option<i32> {
    if b == 0 {
        None
    } else {
        Some(a / b)
    }
}

fn safe_get(v: &Vec<i32>, index: usize) -> Option<&i32> {
    v.get(index)
}

fn validate_username(username: &str) -> Result<String, String> {
    if username.len() < 3 {
        Err(String::from("Username must be at least 3 characters"))
    } else if username.contains(' ') {
        Err(String::from("Username cannot contain spaces"))
    } else {
        Ok(String::from(username))
    }
}

Summary

  • Panic is for unrecoverable errors
  • Use panic! macro to explicitly panic
  • unwrap() and expect() can cause panics
  • Use match, if let, or unwrap_or() for safe handling
  • Enable backtraces with RUST_BACKTRACE=1 for debugging
  • Use panic! for programming errors, Result for recoverable errors
  • Avoid unwrap() in production code
  • Panics unwind the stack and exit the program
  • Use safe alternatives like get() instead of index syntax

What's Next?

Now that you understand panics, you'll learn about Result<T, E> - Rust's way of handling recoverable errors. The Result type allows you to return either a success value or an error, and the caller can decide how to handle it. This is the preferred way to handle errors in Rust.