unwrap: a flaw in Rust's stdlib design?

2 min read Original article ↗

The postmortem of the recent Cloudflare outage revealed that the code path that ultimately failed during the outage made an unwrap call that resulted in an unhandled panic.

If you visit Twitter, you'll see many people jumping to blame Rust for the entire outage. This isn't correct: the outage would have happened regardless (the fundamental, root cause was the Bot Management file generation system producing a file that was too large; an issue that is language-agnostic).

However, it sounds from reading the postmortem that the generic panic error:

thread fl2_worker_thread panicked: called Result::unwrap() on an Err value

made it very difficult for the team to determine what was going on. In fact, the timeline reveals that it took them nearly 2.5 hours to determine that the Bot Management system was at fault: this would almost certainly have been easier to determine with a proper error message.

This is what I want to focus on in this article: as a non-Rust developer (besides hobby projects), I've been very surprised to read discussions that unwrap is commonly used in production Rust codebases. Having looked at popular public Rust crates, this seems to hold true.

Contrast with Go. In Go, using panic in production is almost universally considered to be unacceptable. Having worked with Go across several organizations over many years, I have never worked in one where a panic in prod would make it through a code review.

This, I think, is partially because Go has no easy way of forcing a panic in the same way. This code snippet would have to be something like:

feature_values, err := features.append_with_names(self.config.featureNames)
if err != nil {
    panic(err)
}

Notice that this panic is almost as much effort as just handling the error properly:

feature_values, err := features.append_with_names(self.config.featureNames)
if err != nil {
    return fmt.Errorf("appending feature names failed: %w", err)
}

(Also, notice that the panic sticks out like a sore thumb).

Contrast with the Rust that panicked here:

let (feature_values, _) = features
   .append_with_names(&self.config.feature_names)
   .unwrap()

... which is way easier to write than the correct way of handling the error.

Fundamentally, it seems to me that the design of the Rust stdlib is partially to blame here. It makes it too easy to carelessly throw around unwrap, which leads to a culture where unwrap is seen as acceptable.

Do you agree that unwrap is a flaw in Rust's stdlib design?