3 ms·
> If only there'd be an automatic handler that was invoked every time `?` hits a None, and transformed the error to a richer one, using the context of the entir
by Expurple 1y ago
> If only there'd be an automatic handler that was invoked every time `?` hits a None, and transformed the error to a richer one, using the context of the entire function.
Not sure what you mean by "using the context of the entire function". To me, this "automatic enrichment" sounds like adding a backtrace. If you use `anyhow`, your errors automatically get backtraces. And if you want to preserve error type information, you can easily write your own wrapper:
use std::backtrace::Backtrace;
use std::error::Error;
use std::fmt::{Debug, Display};
use std::num::ParseIntError;
#[derive(Debug)]
struct WithBacktrace<E> {
source: E,
backtrace: Backtrace,
}
impl<E> Display for WithBacktrace<E>
where
E: Display,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.source.fmt(f)
}
}
impl<E> Error for WithBacktrace<E>
where
E: Error + 'static,
{
fn source(&self) -> Option<&(dyn Error + 'static)> {
Some(&self.source)
}
}
impl<E> From<E> for WithBacktrace<E> {
fn from(source: E) -> Self {
Self {
source,
backtrace: Backtrace::capture(),
}
}
}
If you use this type in the function signature, `?` will add backtraces automatically:
fn add_a_backtrace_to_another_error() -> Result<(), WithBacktrace<ParseIntError>> {
let num = "not_a_number".parse::<i64>()?;
Ok(())
}
You can do something similar on `None` too. Although, you can't directly `?` on `None` in a function that returns `Result`:
#[derive(Debug, thiserror::Error)]
#[error("encountered a `None` value")]
struct NoneError;
fn backtrace_on_none() -> Result<(), WithBacktrace<NoneError>> {
None.ok_or(NoneError)?
}