Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

panic-in-production

Level: warn · Article: Errors

Every .unwrap() in production is a bet that this particular call site will never fail. You are wrong about that bet more often than you think, and you find out at the worst possible time.

What it checks

.unwrap(), .expect(), .unwrap_err(), .expect_err(), panic!, unreachable!, todo! and unimplemented! outside the two places the article allows: fn main, where a missing config file may legitimately abort, and test code (see Test code).

Don’t

#![allow(unused)]
fn main() {
fn get_user(id: &UserId, db: &Database) -> User {
    db.query("SELECT * FROM users WHERE id = $1", id).unwrap()
}
}

The signature promises a User. It cannot keep that promise, and the caller has no way to know.

Do

fn get_user(id: &UserId, db: &Database) -> Result<User, NotFoundError> {
    db.query("SELECT * FROM users WHERE id = $1", id)
        .ok_or(NotFoundError { user_id: id.clone() })
}

// Startup: the program cannot run without these. Panicking is honest here.
fn main() {
    let config = load_config().expect("config file required for startup");
    serve(config);
}

Startup is the exception: the program cannot run without its config, and main is the one place where panicking is honest.

Silence it

#![allow(unused)]
fn main() {
let first = items.first().unwrap(); // rabot: allow(panic-in-production) `items` was checked non-empty two lines up
}

An invariant that proves programmer error is the article’s other exception. Say which invariant.