Retain 'anyhow' Result Error Context With 'once_cell::Lazy'
This little problem griped me so hard when I was creating gitnr.
Consider the following:
- We use
once_cell::Lazy
to lazily initialize a static variable - The static variable is of type
anyhow::Result
as that's what we want to return from theLazy
function - The
Lazy
function should it error has additional context or error information we need for debugging
Now by default, if you attempt the code below, should an error occur, the error context will be lost and you will only see a surface level error message.
Are you encountering an error when trying to use a function that returns a Result
within a .map()
closure in Rust? I'm sure this is a common problem people face when learning to use Rust. Thankfully, the solution is actually quite simple.
Problem
Consider the following Rust code where we have two functions, both returning Result types. The first function prefixes a line (a string slice), and the second function takes a list of lines and prefixes each using the first function.
use std::error::Error;
fn prefix_line(line: &str) -> Result<String, Box<dyn Error>> {
Ok(format!("PREFIX: {}", line))
}
fn prefix_each(lines: &[&str]) -> Result<Vec<String>, Box<dyn Error>> {
let result = lines
.iter()
.map(|line| prefix_line(line)?)
.collect::<Vec<_>>();
Ok(result)
}
Attempting to compile this code results in an error on line 10:
the '?' operator can only be used in a closure that returns 'Result' or 'Option' (or another type that implements 'FromResidual') [E0277] Help: the trait 'FromResidual<Result<Infallible, Box<dyn StdError>>>' is not implemented for 'std::string::String'
Breaking down the error:
- The
map
function takes a closure as it's argument - The
?
operator inside the.map()
closure is problematic - This is because the
?
operator expects the closure to return aResult
orOption
- Instead, the closure's return type is inferred to match the expected output type of map, which is
String
- Since
String
doesn't implementFromResidual
, you get the error
Solutions
Collect the results
The simplest way I've found to solve this issue:
- Call
prefix_line(line)
without the?
operator which outputsVec<Result<...>>
- Use
collect::<Result<Vec<_>, _>>()?
to correctly infer types, collect theResults
and propagate errors if any - Leaving us with our desired output
Vec<String>
fn prefix_each(lines: &[&str]) -> Result<Vec<String>, Box<dyn Error>> {
let result = lines
.iter()
.map(|line| prefix_line(line))
.collect::<Result<Vec<_>, _>>()?;
Ok(result)
}
Using a loop
The obvious initial solution but it has drawbacks such as:
- It breaks the functional style
- It requires a mutable variable
fn prefix_each(lines: &[&str]) -> Result<Vec<String>, Box<dyn Error>> {
let mut result = Vec::new();
for line in lines {
result.push(prefix_line(line)?);
}
Ok(result)
}
Using filter_map
filter_map
discardsErr
values collecting only successfulOk
values- Useful if you want to ignore errors and only care about the successful results
fn prefix_each(lines: &[&str]) -> Result<Vec<String>, Box<dyn Error>> {
let result = lines
.iter()
.filter_map(|line| prefix_line(line).ok())
.collect::<Vec<_>>();
Ok(result)
}
Using try_fold
- Use
try_fold
to aggregate results and handle errors gracefully - It allows you to return a Result type directly
fn prefix_each(lines: &[&str]) -> Result<String, Box<dyn Error>> {
let result = lines.iter().try_fold(String::new(), |mut acc, line| {
prefix_line(line).map(|prefixed_line| {
acc.push_str(&prefixed_line);
acc.push_str("\n");
acc
})
})?;
Ok(result)
}
Comments