DEV Community

Subesh Yadav
Subesh Yadav

Posted on

🛠️ Day 15 of #100DaysOfRust – Handling Unrecoverable Errors with panic!

Today’s journey in Rust has been all about embracing unrecoverable errors and understanding how and when to use the powerful panic! macro. Rust is all about safety and stability, and its error handling mechanism is a core part of that philosophy.

🧨 What Is panic!?

Sometimes, something goes wrong in your code, and there’s no sensible way to recover. In these cases, Rust allows your program to panic.

fn main() {
    panic!("crash and burn");
}
Enter fullscreen mode Exit fullscreen mode

When a panic occurs, Rust:

  • Prints an error message
  • Unwinds the stack by default (cleans up memory)
  • Exits the program

You can also configure Rust to abort immediately on panic (faster and useful for smaller binaries):

[profile.release]
panic = 'abort'
Enter fullscreen mode Exit fullscreen mode

🧵 Stack Unwinding vs Aborting

  • Unwinding: Rust walks back up the stack, calling destructors for all in-scope variables.
  • Aborting: Immediate termination, no cleanup. Ideal for performance-critical builds.

To trigger a panic manually, use the panic! macro, or accidentally cause one by doing something unsafe, like out-of-bounds access:

fn main() {
    let v = vec![1, 2, 3];
    v[99]; // Index out of bounds -> panic!
}
Enter fullscreen mode Exit fullscreen mode

This results in:

thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99'
Enter fullscreen mode Exit fullscreen mode

🐞 Debugging with RUST_BACKTRACE

Enable a backtrace for better debugging:

RUST_BACKTRACE=1 cargo run
Enter fullscreen mode Exit fullscreen mode

This shows you exactly which function calls led to the panic, including your own code and any standard/library calls.

🤔 When to Panic and When Not To?

❌ Don’t Panic:

  • When failure is expected (e.g., user input errors, invalid data from a file, network errors).
  • Prefer returning a Result so the caller can decide how to recover.
fn read_file(path: &str) -> Result<String, std::io::Error> {
    std::fs::read_to_string(path)
}
Enter fullscreen mode Exit fullscreen mode

✅ Panic When:

  • In tests, prototypes, or examples.
  • For contract violations or unreachable code.
  • When encountering unrecoverable bugs (e.g., invalid logic, impossible states).

📌 Practical Guidelines for panic!

Use panic!:

  • When the program is in an invalid state that can’t be safely continued.
  • For violations of internal assumptions or invariants.
  • When the error is due to a bug in the caller’s code (e.g., passing an invalid value).

🧪 Using unwrap() and expect()

These methods on Option and Result panic on failure. They're great for:

  • Prototypes
  • Tests
  • Code where failure shouldn’t happen
let ip: std::net::IpAddr = "127.0.0.1"
    .parse()
    .expect("Hardcoded IP should be valid");
Enter fullscreen mode Exit fullscreen mode

⚠️ Be careful—only use them when you’re 100% sure the result is valid, and document the assumption.

🔐 Preventing Panics via Type System

Rust allows you to model correctness in types, reducing runtime checks.

Let’s take the example of a number guessing game, where a valid guess is between 1 and 100.

Instead of checking in every function:

if guess < 1 || guess > 100 {
    panic!("Guess out of range");
}
Enter fullscreen mode Exit fullscreen mode

Create a type with validation logic:

pub struct Guess {
    value: i32,
}

impl Guess {
    pub fn new(value: i32) -> Guess {
        if value < 1 || value > 100 {
            panic!("Guess value must be between 1 and 100, got {value}");
        }
        Guess { value }
    }

    pub fn value(&self) -> i32 {
        self.value
    }
}
Enter fullscreen mode Exit fullscreen mode

This pattern:

  • Encapsulates logic
  • Guarantees valid state
  • Reduces boilerplate

🧠 Summary

Rust's panic system provides a safe fallback when your code is in an irrecoverable state. Here’s what we learned today:

  • Use panic! to exit when things go horribly wrong.
  • Use Result to give callers the choice of how to handle errors.
  • unwrap and expect are useful for quick prototyping, testing, and known-valid values.
  • Use Rust’s type system to validate data at compile time, minimizing runtime checks.
  • Debug better with RUST_BACKTRACE.
  • Think like Rust: fail early, fail loudly, and ensure safety above all.

Stay tuned for Day 16, where we’ll begin our dive into recoverable errors using Result and error handling idioms.

If you’re learning Rust too, let’s connect! 👇

Top comments (0)