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:
.unwrap()
serve as concise, readable placeholders..unwrap() and .expect() allow rapid iteration
before deciding on a comprehensive error-handling strategy..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.
// 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.
// 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.