Collaborative Summary: 3 Fact Finding Pre-RFCs around Error Handling / Reporting
This document is an attempt at refining 3 proposals for changes to Error Handling and Reporting. I’ll be following the the process described in this blog post by Niko Matsakis

Participation Guidelines


The participation guidelines are the same as in Niko’s dropbox paper here, If you’re unfamiliar with it, please check out the first section there for an in depth explanation of the goals and spirit of the process. 

Problems

  • When reporting errors its common to want to extract context about an error, such as a dyn Error cause, or a Backtrace, but the current approach requires adding a new fn to the Error trait for each additional form of context found generally useful when reporting Errors.
  • The std::backtrace::Backtrace type is expensive to construct and captures information about frames irrelevant to the developer when debugging errors, and isn’t available on certain platforms.
  • Using error reporting types that impl Debug as a human readable error report so they can be nicely reported from main end up looking strange / out of place when formatted in Debug representations of other types. [1]

Proposals


  • Add support for extracting generic member variables from behind dyn Error trait objects, essentially a generalization of the current backtrace and source functions
  • Add support for Error Return Traces, similar to zig’s
  • Differentiate Debug::fmt from Error Reporting, either with a new flag on fmt::Formatter or with a new trait alongside Display/Debug

Details of the problem statement, possible solutions, unresolved issues, and pros and cons for each solution will be placed below.

Generalized member access for dyn Error trait objects

Problem Statement

When reporting dyn Errors to an end user it is common to want to extract certain forms of context that are frequently gathered to give more specific information about the state of the system that resulted in the specific failure.

The most common form of context is a Backtrace, which indicates where in the program an error originated from, which is often useful to developers when debugging, and is common enough that it has been added to the Error trait as fn backtrace(&self) → Option<&Backtrace>.  Similarly the source and cause functions can be seen as other forms of context, one which is useful to end users who are trying to understand why an error happened and how to prevent it. This idea is supported by the similarity between the signatures for cause, source, and backtrace, all of which borrow the error and return an optional reference to a member variable. All of these functions exist to be used by an Error Reporting type which checks if errors already captured a backtrace and iterates through each error and prints the full chain of causes.

However, this doesn’t leave room for reporting types of context from errors that aren’t so widely used as to make it into the standard library. One such example is SpanTrace, a backtrace like type from the tracing-error library. Another type that might be useful to extract is the return trace Location information that is gathered as part of the Error Return Traces proposal.

Potential Designs + pros/cons

Solution 1
  • Add a function to trait Error with the signature fn(&self, TypeID) → Option<&dyn Any> and a function on dyn Error with the signature fn<T>(&self) → Option<&T> which calls the trait fn and with the typeid of T and downcasts the return value to T.

Example

#![allow(unused_variables)]
#![feature(backtrace)]
use std::any::{Any, TypeId};
use std::backtrace::Backtrace;
use tracing_error::SpanTrace;
use std::fmt::Debug;

pub trait ErrorContext: Debug {
    fn any_ref(&self, id: TypeId) -> Option<&dyn Any> {
        None
    }
}

impl dyn ErrorContext {
    pub fn context_ref<T: Any>(&self) -> Option<&T> {
        self.any_ref(TypeId::of::<T>())?.downcast_ref::<T>()
    }
}