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

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 Err arm: Err(_) => {}
  • an empty if let Err(..) = .. {}
  • .ok(); as a statement, which converts the Result to an Option and 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();
}