Helper for passing extra context to Errors

Here's how I tend to do it already: defer the expensive parts until the implicit into and/or until you actually display things. And From<(SrcErrWithContext, &CheapThing)> is often a part of that.

Adapted from a project:

pub struct FileReadError {
    path: PathBuf,
    kind: FileReadErrorKind,
}

enum FileReadErrorKind {
    Open(io::Error), // io::Error is too general, so these
    Read(io::Error), // supply more context
    Parse(FileLineError),
}

The expensive parts:

impl From<(FileReadErrorKind, &Path)> for FileReadError {
    fn from((kind, path): (FileReadErrorKind, &Path)) -> Self {
        let path = path.to_owned();
        Self { path, kind }
    }
}

impl Error for FileReadError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        // (return the io::Error etc .. xor include them in Display below)
    }
}

impl fmt::Display for FileReadError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        use FileReadErrorKind as Kind;
        match self.kind {
            Kind::Open(_) => write!(f, "could not open {} for reading", self.path.display()),
            // ...
        }
    }
}

What using the infrastructure looks like:

pub fn open(path: &Path) -> Result<Self, FileReadError> {
    use FileReadErrorKind as Kind;
    // Today / with zip_err
    let file = File::open(path).map_err(|e| (Kind::Open(e), path))?;
    let file = File::open(path).map_err(Kind::Open).zip_err(path)?;
    // ...

    // Today / with zip_err
    this.read_inner(file).map_err(|e| (e, path))?;
    this.read_inner(file).zip_err(path)?;

    Ok(this)
}
fn read_inner(&mut self, mut file: File) -> Result<(), FileReadErrorKind> {
    // ...
}
1 Like