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. Something like... and exception handler :)
Java and Python use stacks of exception handlers that catch and re-throw exceptions, chaining them and adorning with contextual information. It works very well: you get a stack trace, and a number of human-oriented messages with relevant context.
Unfortunately, the whole idea of walking up the stack and looking for an exception handler crumbles down once you touch async calls, as evidenced by JS/TS and C#'s LINQ. It's almost as if you want to pass a context object to any fallible function, to be logged if an error happens. It's very similar to passing other context, like the `this` pointer in OOP, or implicits in Scala (uhg!). Lifetime of such an object is also going to be an interesting problem, because you likely don't want to copy it all the time, so it must be mutable.
> 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`:
Java and Python use stacks of exception handlers that catch and re-throw exceptions, chaining them and adorning with contextual information. It works very well: you get a stack trace, and a number of human-oriented messages with relevant context.
Unfortunately, the whole idea of walking up the stack and looking for an exception handler crumbles down once you touch async calls, as evidenced by JS/TS and C#'s LINQ. It's almost as if you want to pass a context object to any fallible function, to be logged if an error happens. It's very similar to passing other context, like the `this` pointer in OOP, or implicits in Scala (uhg!). Lifetime of such an object is also going to be an interesting problem, because you likely don't want to copy it all the time, so it must be mutable.