Pre-RFC: Generalizing the `backtrace` fn on `std::error::Error`

Rough sketch, I'd like to generalize the backtrace fn to instead return arbitrary types.

Example

Heres what the trait and impls might look like

#![feature(backtrace)]
use std::any::{Any, TypeId};
use std::backtrace::Backtrace;

pub trait Error {
    fn context_raw(&self, id: TypeId) -> Option<&dyn Any>;
}

impl dyn Error {
    pub fn context<T: Any>(&self) -> Option<&T> {
        self.context_raw(TypeId::of::<T>())?.downcast_ref::<T>()
    }
}

struct Blah {
    context: Backtrace,
    more_context: String,
}

impl Error for Blah {
    fn context_raw(&self, id: TypeId) -> Option<&dyn Any> {
        if id == TypeId::of::<Backtrace>() {
            Some(&self.context)
        } else if id == TypeId::of::<String>() {
            Some(&self.more_context)
        } else {
            None
        }
    }
}

And using it would look like this

fn test_context() {
    let obj = Blah {
        context: Backtrace::capture(),
        more_context: "There is something you should really know about this error...".into(),
    };
    let traitobj: Box<dyn Error> = Box::new(obj);
    let context: Option<&Backtrace> = traitobj.context();
    let more_context: Option<&String> = traitobj.context();

    if let Some(context) = context {
        println!("{}", context);
    }

    if let Some(context) = more_context {
        println!("{}", context);
    }
}

Motivation

The main motiviation for this is that I'd like to be able to extract tracing_error::SpanTraces from &dyn Error trait objects, which are analogous to backtraces. AFAIK doing this with backtrace was the original motivation for the Fail trait, so this seems like an extension of previous work.

Another benefit is that it may make the error trait easier to move out of std into either alloc or core in the future. I'm told that backtrace already supports no_std so this is a nonissue Edit: apparently backtrace is still an issue for moving the Error trait to alloc based on this issue but now I'm confused as to why...

Issues

I imagine that renaming the backtrace fn, even though its technically unstable, would be a pretty big change ecosystem wide. It might make more sense to add the context method on the side rather than replacing the backtrace method entirely, though I worry that this will interfere with moving the error::Error trait out of std in the future.

Also, the context fn identifier is relatively well used throughout the error handling ecosystem, namely anyhow and snafu, though afaik they mostly use it as a helper for map_err! to wrap an inner error type. This would also cause friction with the ecosystem so it maybe desirable to pick a different name than context.

Overall this is definitely a usability regression in terms of working with the error trait, now you have to work with type_id's and the Any trait which may make working with the error trait even more confusing for beginners.

4 Likes

Being able to make progress on https://github.com/rust-lang/rust/issues/62502 (disclaimer: I filed this issue) is a substantial benefit to people who develop crates that support no_std. Per that issue's reason for closure (requiring an RFC to figure out a way forward), I think you're a bit hasty in assuming that it's a non-issue, but maybe I'm mistaken.

edited the OP accordingly

1 Like

Instead of calling the fn to retrieve a member context() an alternative could be something like either member() or member_ref()

Did you mean for the context types to be switched? The struct's context member's type isBacktrace, while its more_context member's type is String. The variables in the function have the opposite name+type combination.

Heh, no I'll fix that, ty