swallowed-error
Level: warn · Article: Errors
Every silent catch is a future 3am.
What it checks
Three shapes that make a failure disappear:
- an empty
Errarm:Err(_) => {} - an empty
if let Err(..) = .. {} .ok();as a statement, which converts theResultto anOptionand throws it away
Don’t
#![allow(unused)]
fn main() {
impl Cache {
fn evict(&self, key: &Key) {
match self.invalidate(key) {
Ok(()) => {}
Err(_) => {} // shouldn't happen
}
std::fs::remove_file(self.path_for(key)).ok();
}
}
}
It happened. The comment lied. Somebody will spend four hours finding which branch swallowed it.
Do
#![allow(unused)]
fn main() {
impl Cache {
fn evict(&self, key: &Key) -> Result<(), EvictError> {
if let Err(error) = self.invalidate(key) {
warn!(%key, %error, "cache entry survives invalidation; serving stale until TTL");
}
std::fs::remove_file(self.path_for(key))?;
Ok(())
}
}
}
Propagate it, or log it with the context the reader at 3am needs. Either way, the failure leaves a trace.
Silence it
#![allow(unused)]
fn main() {
// rabot: allow(swallowed-error) best-effort cleanup of a temp file; the OS reclaims it anyway
std::fs::remove_file(&tmp).ok();
}