Don’t Panic
Errors in production systems belong to two distinct domains: expected
state failures and broken system invariants. Result handles
state. panic! handles broken guarantees.
The Rust Book establishes this separation in Chapter
9 (“Error Handling”) by splitting failures into recoverable
(Result<T, E>, Option<T>) and
unrecoverable (panic!) paths.
Chapter 9 identifies specific contexts where panicking may be highly appropriate:
- Examples: Robust error handling can obscure the
core concept being illustrated. Methods like
.unwrap()serve as concise, readable placeholders. - Prototyping: In the early stages of development,
.unwrap()and.expect()allow rapid iteration before deciding on a comprehensive error-handling strategy. - Tests: When writing unit or integration tests,
calling
.unwrap()is expected. A test must fail immediately if a setup operation or dependency fails.
While these shortcuts help with quick learning and prototyping, they
must not leak into production. Methods like .unwrap() and
.expect() turn expected runtime failures into system
panics. They blur the boundary between operational error handling and
process crashes. This risk is not merely theoretical; the Cloudflare
outage on 18 November 2025 was caused by an unhandled
.unwrap() on a None value, triggering a
cascade of failures across their global network.
Poor Implementation:
// Bad: Converts runtime IO failure into process termination
let file = File::open("config.json").unwrap();Explicit error propagation surfaces failure context to caller sites where recovery belongs.
Improved Implementation:
// Good: Preserves caller control and error context
let file = File::open("config.json").unwrap_or_default(|e| {
eprintln!("Failed to open config.json: {}", e);
// Decide whether to recover, log, or propagate the error
});The responsibility for handling the error is now explicit, the call site decides whether to recover, log, or propagate the error further.
In safety-critical software, an unhandled panic is not a minor bug; it is a violation of fundamental safety properties. Safety-critical engineering requires total determinism, explicit error domains, and zero unhandled failure states at compile time. Allowing arbitrary panics breaks process guarantees and risks catastrophic state corruption.
Safety enforcement belongs in the build pipeline. Enforce compile-time panic elimination across the codebase using explicit Clippy lints:
#![deny(clippy::unwrap_used)]
#![deny(clippy::expect_used)]
#![deny(clippy::panic)]Static analysis converts runtime panic risks into immediate compilation errors.
Panics are not an alternative control flow. Use panic!
or assert! exclusively when continuing execution would
compromise memory safety or corrupt internal state. A panic at an
execution boundary must never leak across foreign interfaces or crash
orchestration services without isolation. Catch panics at process
boundaries, convert them into explicitly structured error types, and
isolate the failure.