# Helper for passing extra context to Errors

**URL:** <https://internals.rust-lang.org/t/helper-for-passing-extra-context-to-errors/20259>\
**Category:** libs\
**Created:** [February 4, 2024, 11:08am UTC](https://internals.rust-lang.org/t/helper-for-passing-extra-context-to-errors/20259 "2024-02-04T11:08:56Z")\
**Posts on this page:** 17\
**Page:** 1

<div class="post-metadata">

**Author:** ![kornel](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/kornel/32/2711_2.png) [@kornel](https://internals.rust-lang.org/u/kornel)\
**Post date:** [February 4, 2024, 11:08am UTC](https://internals.rust-lang.org/t/helper-for-passing-extra-context-to-errors/20259/1 "2024-02-04T11:08:56Z")

</div>

When errors implement `From`, propagation of errors is very easy and terse:

```rust
do_this(arg1)?.do_that(arg2)?;

```

However, the `From` impl is context-free, and some errors aren't informative on their own (the `io::Error` without a filename!)

`anyhow` helps here by having a `.context()` method, but that works only for `anyhow`'s own error type. For libraries using specific `enum Error` (`thiserror`-style) this isn't as neat:

```rust
do_this(arg1).map_err(|e| Error::new(e, arg1))?.do_that(b).map_err(|e| Error::new(e, arg2))?;

```

And something like `.context()` would require extra traits and bespoke implementations.

I wonder if the standard library could provide some universal helper method to cut down on the syntactic noise of `map_err(|_|)`.

For example, there's `option.zip(new_val)` that makes `Some((previous, new_val))`. What if `Result` had `.zip`? It would be possible to implement `From<(Error, Context)> for CustomError`, even by boilerplate-generating libraries, so code like this could work:

```rust
do_this(arg1).zip(arg1)?.do_that(b).zip(arg2)?;

```

For example imagine `impl From<(io::Error, PathBuf)> for ErrorWithPath` + `zip`:

```rust
fs::File::open(&path).zip(path)?

```

`zip` is not the best name for it, but maybe a better-named similar pattern is possible?

---

<div class="post-metadata">

**Author:** ![8573](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/8573/32/4913_2.png) [@8573](https://internals.rust-lang.org/u/8573)\
**Post date:** [February 4, 2024, 7:59pm UTC](https://internals.rust-lang.org/t/helper-for-passing-extra-context-to-errors/20259/2 "2024-02-04T19:59:20Z")

</div>

> [@kornel](#):
>
> `anyhow` helps here by having a `.context()` method, but that works only for `anyhow`'s own error type. For libraries using specific `enum Error` (`thiserror`-style) this isn't as neat

Some prior art I feel ought to be mentioned is @shepmaster's [`snafu`](https://lib.rs/crates/snafu), which covers both sides of the `anyhow`/`thiserror` divide but prefers a style that is `thiserror`-style plus `anyhow`-style helper methods, including `.context()`.

---

<div class="post-metadata">

**Author:** ![kpreid](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/kpreid/32/8484_2.png) [@kpreid](https://internals.rust-lang.org/u/kpreid)\
**Post date:** [February 5, 2024, 5:18am UTC](https://internals.rust-lang.org/t/helper-for-passing-extra-context-to-errors/20259/3 "2024-02-05T05:18:17Z")

</div>

I really like the shape of the `zip` idea because it means that the correct type of error-with-more-context can be determined by the _combination_ of the existing error type and the type of context provided — there's a lot of potential for error types making it ergonomic to use them well.

However, I worry that there might be quirks lurking in the trait coherence rules and type inference, such that it might be better to have a dedicated trait for the purpose rather than `From` in particular — so whether or not that specifically is true, it would be good to test the limits imposed by the chosen trait. For example, what happens if you try to blanket implement for a concrete context type and all error types? For a concrete error and all contexts? I'm not confident enough in my understanding of the trait coherence rules to know the answers already, and probably few people are, so concrete examples of what is and isn't possible would probably make a good component of a future RFC.

---

<div class="post-metadata">

**Author:** ![programmerjake](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/programmerjake/32/5893_2.png) [@programmerjake](https://internals.rust-lang.org/u/programmerjake)\
**Post date:** [February 5, 2024, 6:34am UTC](https://internals.rust-lang.org/t/helper-for-passing-extra-context-to-errors/20259/4 "2024-02-05T06:34:49Z")

</div>

if the method literally is `Err(v).zip_thing(v2)` to `Err((v, v2))`, then I think an appropriate name is `zip_err`, to go along with `map_err` that we already have

---

<div class="post-metadata">

**Author:** ![dlight](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/dlight/32/8462_2.png) [@dlight](https://internals.rust-lang.org/u/dlight)\
**Post date:** [February 5, 2024, 1:02pm UTC](https://internals.rust-lang.org/t/helper-for-passing-extra-context-to-errors/20259/5 "2024-02-05T13:02:17Z")

</div>

I love this, but what happens if I zip twice?

```rust
myresult.zip_err(additional_info1).zip_err(additional_info2)

```

In my mind ideally it should result in a triple `Result<T, (OriginalErr, Info1, Info2)>`, but then writing the method becomes very complicated (not impossible, but would require complicated type level gymnastics), with poor type error messages (unless it is special cased somehow)

But with the vanilla idea it would be `Result<T, ((OriginalErr, Info1), Info2)>`, which is probably not too bad.

This probably showcases the need for either variadic generics or type level lists (like frunk's `HList`, which semantically are the same as tuples, but that you can pattern match at the type level with `HCons` and `HNil`)

---

<div class="post-metadata">

**Author:** ![dlight](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/dlight/32/8462_2.png) [@dlight](https://internals.rust-lang.org/u/dlight)\
**Post date:** [February 5, 2024, 1:04pm UTC](https://internals.rust-lang.org/t/helper-for-passing-extra-context-to-errors/20259/6 "2024-02-05T13:04:30Z")

</div>

Also, should pairs `(Error, Info)` be used here, or another type that conveys what it is used for?

If it were something like `ErrorContext<OriginalError, Info>`, it would quickly be too noisy if you nested zips. So it should be pairs probably.

---

<div class="post-metadata">

**Author:** ![kornel](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/kornel/32/2711_2.png) [@kornel](https://internals.rust-lang.org/u/kornel)\
**Post date:** [February 5, 2024, 4:24pm UTC](https://internals.rust-lang.org/t/helper-for-passing-extra-context-to-errors/20259/7 "2024-02-05T16:24:40Z")

</div>

> [@kpreid](#):
>
> However, I worry that there might be quirks lurking in the trait coherence rules and type inference

It actually works fine and is super flexible.

The trait is in the form of `From<T> for MyCustomErrorType`, and because a crate-local type is on the right side of `for`, the `From` part can be almost anything. The only conflicting blanket implementation is `From<MyCustomErrorType> for MyCustomErrorType`, so as long as you use a tuple in `From<(T, U)>`, it can contain anything.

> [@kpreid](#):
>
> dedicated trait for the purpose rather than `From` in particular

I chose `From` for integration with the `?` operator, because `result.zip_err(x)?` will automatically apply `ErrorInFunctionReturnType::from((err, x))`.

Admittedly, (re)use of a tuple for this purpose is "clever". OTOH `From` is already used for error conversion, and another trait would need to be very `From`-like anyway:

```rust
impl ContextFrom<E, C> for MyCustomError {
   fn from_context(error: E, context: C) -> Self { … }
}

```

It doesn't use associated types, because you may want to convert `(io::Error, PathBuf)` to `MyReadError` or `MyWriteError` or `CantFindConfigFileError` and so on.

It does rely on the function's error type to be specific, but that's nothing new. When the error type is generic/unknown Rust already needs hints for the `From` conversion.

> [@dlight](#):
>
> what happens if I zip twice?

Rust generally doesn't flatten tuples. `.zip` behaves in the same nested way on `Option` and `Iterator`, so even though it's non-ideal to have lisp-looking nested list, I think zipping of errors should stick to that for simplicity and consistency. In practice people would probably use `.zip((info1, info2))` or proper `map_err` if there's complex context to add.

---

<div class="post-metadata">

**Author:** ![kpreid](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/kpreid/32/8484_2.png) [@kpreid](https://internals.rust-lang.org/u/kpreid)\
**Post date:** [February 5, 2024, 4:39pm UTC](https://internals.rust-lang.org/t/helper-for-passing-extra-context-to-errors/20259/8 "2024-02-05T16:39:34Z")

</div>

> [@kornel](#):
>
> It actually works fine and is super flexible.
> 
> The trait is in the form of `From<T> for MyCustomErrorType` , …

Okay, but **for example** , what if you want to produce a _different library's error type_, because you're in the position of adapting _to_ an existing type instead of adapting _from_ an existing type? In general, I'm not saying this is going to be a problem; I'm saying the bounds of what will be possible given the design should, eventually, be thoroughly described in the proposal.

---

<div class="post-metadata">

**Author:** ![kornel](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/kornel/32/2711_2.png) [@kornel](https://internals.rust-lang.org/u/kornel)\
**Post date:** [February 5, 2024, 5:16pm UTC](https://internals.rust-lang.org/t/helper-for-passing-extra-context-to-errors/20259/9 "2024-02-05T17:16:25Z")

</div>

Ah, in this case indeed:

```rust
impl From<(MyError, T)> for io::Error

```

is not allowed, but:

```rust
impl ContextFrom<MyError, T> for io::Error

```

is allowed, because trait coherence is defined in terms of type arguments in the trait, and not type arguments in tuples (they don't get _fundamental_ type exception).

---

<div class="post-metadata">

**Author:** ![harmic](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/harmic/32/5750_2.png) [@harmic](https://internals.rust-lang.org/u/harmic)\
**Post date:** [February 6, 2024, 3:15am UTC](https://internals.rust-lang.org/t/helper-for-passing-extra-context-to-errors/20259/10 "2024-02-06T03:15:18Z")

</div>

When I am trying to add context to an error, often I'm trying to add two things: the object I was operating on, and what I was trying to do with it. A common pattern would be:

```
let file = open(config_file).zip_err(format!("Opening config file '{config_file}'"))?;

```

The problem with that is that the context gets prepared in the non-error case as well. `map_err` bypasses this by taking a closure, as does `with_context` if using anyhow.

How would you do that using this pattern?

---

<div class="post-metadata">

**Author:** ![quinedot](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/quinedot/32/7294_2.png) [@quinedot](https://internals.rust-lang.org/u/quinedot)\
**Post date:** [February 6, 2024, 4:32am UTC](https://internals.rust-lang.org/t/helper-for-passing-extra-context-to-errors/20259/11 "2024-02-06T04:32:36Z")

</div>

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:

```rust
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:

```rust
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:

```rust
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> {
    // ...
}

```

---

<div class="post-metadata">

**Author:** ![Jon-Davis](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/jon-davis/32/5453_2.png) [@Jon-Davis](https://internals.rust-lang.org/u/Jon-Davis)\
**Post date:** [February 6, 2024, 5:33am UTC](https://internals.rust-lang.org/t/helper-for-passing-extra-context-to-errors/20259/12 "2024-02-06T05:33:01Z")

</div>

I imagine this could be adapted to use the same pattern as `with_context`

```rust
let file = open(config_file)
  .with_zip_err(|| format!("Opening config file '{config_file}'"))?;

```

with a `thiserror` style Error of

```rust
#[derive(Error, Debug)]
enum Error {
    #[error("IO Error of: {0} with context of {1:?}")]
    Io(#[from] std::io::Error, #[context] Option<String>)
}

```

---

<div class="post-metadata">

**Author:** ![programmerjake](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/programmerjake/32/5893_2.png) [@programmerjake](https://internals.rust-lang.org/u/programmerjake)\
**Post date:** [February 6, 2024, 6:12am UTC](https://internals.rust-lang.org/t/helper-for-passing-extra-context-to-errors/20259/13 "2024-02-06T06:12:10Z")

</div>

well, you could just do the formatting in the `From` implementation:

```rust
struct MyError {
    io_err: io::Error,
    context_str: String,
}

impl From<(io::Error, fmt::Arguments<'_>)> for MyError {
    fn from((io_err, context): (io::Error, fmt::Arguments<'_>)) -> Self {
        Self { io_err, context_str: context.to_string() } // does the expensive formatting here
    }
}

// constructing fmt::Arguments is cheap, no lambda function needed
let file = open(&path_to_read).zip_err(format_args!("trying to open {path_to_read}"))?;

```

---

<div class="post-metadata">

**Author:** ![8573](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/8573/32/4913_2.png) [@8573](https://internals.rust-lang.org/u/8573)\
**Post date:** [February 6, 2024, 7:01am UTC](https://internals.rust-lang.org/t/helper-for-passing-extra-context-to-errors/20259/14 "2024-02-06T07:01:17Z")

</div>

> [@](#):
>
> defer the expensive parts until the implicit `into`

This is the path `snafu` takes, although it uses named, macro-generated types rather than tuples.

---

<div class="post-metadata">

**Author:** ![kornel](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/kornel/32/2711_2.png) [@kornel](https://internals.rust-lang.org/u/kornel)\
**Post date:** [February 6, 2024, 11:33am UTC](https://internals.rust-lang.org/t/helper-for-passing-extra-context-to-errors/20259/15 "2024-02-06T11:33:07Z")

</div>

[Option has `zip_with`](https://doc.rust-lang.org/stable/std/option/enum.Option.html#method.zip_with), so `Result` could have `zip_err_with` too.

Deferring work until `From`/`Into` sounds like a good idea. There's even a non-allocating `format_args!` that can be used to defer string formatting, while remaining in the same scope. `From<(E, impl Display)>` could handle all string types + `format_args!`.

---

<div class="post-metadata">

**Author:** ![Jon-Davis](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/jon-davis/32/5453_2.png) [@Jon-Davis](https://internals.rust-lang.org/u/Jon-Davis)\
**Post date:** [February 6, 2024, 3:27pm UTC](https://internals.rust-lang.org/t/helper-for-passing-extra-context-to-errors/20259/16 "2024-02-06T15:27:07Z")

</div>

> [@programmerjake](#):
>
> well, you could just do the formatting in the `From` implementation:

That works if every context is going to be converted to a String, but I imagine there would be a desire to support non-string contexts as well.

> **Code Example**
>
> ```rust
> let entry = query!(include_str!("fetch_by_id.sql"))
> .bind(id)
> .fetch_optional(&pool)
> .await
> .zip_err_with(|| SqlContext::Fetch(id.into()))?;
> 
> let result = query!(include_str!("update_value.sql"))
> .bind(id)
> .bind(value)
> .execute(&pool)
> .await
> .zip_err_with(|| SqlContext::Update(id.into(), value))?;
> 
> #[derive(Context, Debug)]
> enum SqlContext {
> #[context("Failed to read id: {0}")]
> Fetch(InlineableString),
> #[context("Failed to update id: {0} to value of {1}")]
> Update(InlineableString, isize),
> }
> 
> #[derive(Error, Debug)]
> enum Error {
> #[error("sql error occurred {0}")]
> Sql(#[from] sqlx::Error, #[context] Option<SqlContext>),
> }
> 
> ```

I feel if `zip_err` is added, than `zip_err_with` should also be added. Whether or not the `From` implementation includes an implicit clone is up to the different error crate developers.

---

<div class="post-metadata">

**Author:** ![system](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/system/32/14092_2.png) [@system](https://internals.rust-lang.org/u/system)\
**Post date:** [May 6, 2024, 3:27pm UTC](https://internals.rust-lang.org/t/helper-for-passing-extra-context-to-errors/20259/17 "2024-05-06T15:27:28Z")

</div>

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.
