dropped-error-context
Level: warn · Article: Errors
Every nested level loses context. Every silent catch is a future 3am.
What it checks
.map_err(|_| ..), .or_else(|_| ..) or .unwrap_or_else(|_| ..) whose
closure ignores the error it receives: a parameter named _ or starting
with _. The original failure, the one with the file name and the OS
message, is gone before anyone reads it.
Don’t
#![allow(unused)]
fn main() {
impl Config {
fn load(path: &Path) -> Result<Self, ConfigError> {
let text = std::fs::read_to_string(path).map_err(|_| ConfigError::Unreadable)?;
text.parse()
}
}
}
“Config unreadable.” Which file? Permission denied, or not found, or a directory? The error that knew is gone.
Do
#![allow(unused)]
fn main() {
enum ConfigError {
Unreadable {
path: PathBuf,
#[source]
source: std::io::Error,
},
}
impl Config {
fn load(path: &Path) -> Result<Self, ConfigError> {
let text = std::fs::read_to_string(path).map_err(|source| ConfigError::Unreadable {
path: path.to_path_buf(),
source,
})?;
text.parse()
}
}
}
Or with thiserror, #[from] and ? do it without a closure at all. The
caller matches on your variant; the log walks source() down to the OS.
Silence it
#![allow(unused)]
fn main() {
// rabot: allow(dropped-error-context) the only possible failure is Utf8; the position is what matters
let name = String::from_utf8(bytes).map_err(|_| NameError::NotUtf8 { offset })?;
}