Shorter syntax for adding context in Result's error?


I find it useful to be able to add more context in the error type of Result, while propagating it.

Imagine error message like this: "Failed to read_settings(): app.json: Failed to download_file_from_url() Failed to connect() to ip connection refused"

You can see that each of the functions involved in propagation (read_settings(), download_file_from_url(), connect()) adds something useful, usually value of it parameter, such as app.json and and It makes the error message much more useful and actionable than just "too many open files".

Unfortunately, using the ? operator in the callstack above would lead to an error like "too many open files" returned from function read_settings(), which is not helpful.

Solution in C++

For C++ I implemented Result type (based on coroutines) that has a function that:

  • either returns OK value
  • or returns from the function error and adds provided arguments to the error type.
TResult<int, std::string> ParseNumber(std::string_view FileName);

TResult<int, std::string> ReadSettings(std::string_view FileName) {
   int Int = co_await ParseNumber(FileName).OrPrependErrMsgAndReturn();
   return std::max(1234, Int);

The crux is in OrPrependErrMsgAndReturn() by default it automatically prepends Failed to <FUNCTION_NAME>: <LINE>, which already gives us callstack. But you can also provide additional string explanation.


What is the best way to achieve something similar in Rust?

I need something like:

fn read_settings(path: &Path) -> Result<i32, ErrorDesc> {
   let int = parse_number(file_path)?("path is: {path}")
//                                  ^^^^^^^^^^^^^^^^^^^^
// I need this to add current source location as well as path in
//              ErrorDesc and return it from read_settings

Assuming you already know how / implemented a way of adding an additional explanatory string of extra context information to your ErrorDesc type, you can do (a variant of) the approach that the anyhow trait does, defining an extension trait for Result<T, ErrorDesc>: Context in anyhow - Rust

The call, if such a .context method you write expected String, could then look something like

fn read_settings(path: &Path) -> Result<i32, ErrorDesc> {
   let int = parse_number(file_path).context(|| format!("path is: {path}"))?;

(I’m assuming using a closure to avoid the overhead of crating such a string in the non-error case. Following anyhow more closely, one could also offer a generic C: Display argument, in which format_args! could replace format! and could possibly save some overhead of creating the String; though as we’re concerned with error handling (i.e. not the common case of execution), such overhead is likely negligible anyways, and String is easier to understand.)

Rust Playground

1 Like

A little example that just came up here:

use anyhow::{Context, Result};
use std::cmp::Ordering;

fn get_file_size(path: &str) -> Result<u64> {
    let size = std::fs::metadata(path)
        .with_context(|| format!("Failed to read metadata for {}", path))?


pub fn compare_file_sizes(path_a: &str, path_b: &str) -> Result<Ordering> {
    let size_a = get_file_size(path_a)?;
    let size_b = get_file_size(path_b)?;

fn main() -> Result<()> {
    let path_a = "path_a";
    let path_b = "path_b";
    let ordering = compare_file_sizes(path_a, path_b)
        .with_context(|| format!("Comparing size of files {}, {}:", path_a, path_b))?;

    // Do something with file size ordering...


Produce the nice error message when a file is missing:

Error: Comparing size of files path_a, path_b:

Caused by:
    0: Failed to read metadata for path_b
    1: No such file or directory (os error 2)

@steffahn @ZiCog

Nice! Thank you.
This is what I have been looking for.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.