-
Notifications
You must be signed in to change notification settings - Fork 103
Description
Related Problem
Nesting enums is an effective strategy for providing errors from a library, allowing the downstream user to use arbitrarily granular handling. e.g.
struct Err1 {}
enum Err2 {
Downstream(Err1),
Originating
}
However, this does not seem possible in Report
. I believe the way to emulate this for functions producing reports is as follows:
struct Err1 {}
enum Err2 {
Downstream,
Originating
}
fn foo() {
let orig_report = Report::new(Err1);
let top_report = orig_report.change_context(Err2::Downstream);
match top_report.current_context() {
Downstream => {
let orig_err = top_report.downcast_ref::<Err1>().unwrap();
...
},
Originating => { ... }
}
}
This requires the library author to document the downstream error and the user to read that documentation. Standard error types have no such limitation.
Proposed Solution
Frames
are currently stored as Box<dyn FrameImpl>
with mutable handle methods. Make this type Pin<Box<dyn FrameImpl>>
and replace mutable access methods with https://doc.rust-lang.org/std/pin/struct.Pin.html#method.set wrappers. Report
can then be self-referential with its errors, enabling Deref
access through some const *
wrapper (ReportCell
) and a special construction method (change_context_ref
) that provides a reference to the current context.
struct Err1 {}
enum Err2 {
Downstream(ReportCell<Err1>),
Originating
}
fn foo() {
let orig_report = Report::new(Err1);
let top_report = orig_report.change_context_ref(|err_ref| Err2::Downstream(err_ref));
match top_report.current_context() {
Downstream(orig_err_ref) => {
let orig_err: &Err1 = *orig_err_ref;
...
},
Originating => { ... }
}
}
For soundness when a Context
uses borrowed data during drop, Report
would need to be changed from Box<Vec<Frame>>
to Box<Vec<ManuallyDrop<Frame>>>
and given a custom Drop
implementation that enforces top of stack to bottom of stack drop ordering.
This scheme would allow for both enum matching and mutation (via replacement) for purposes outlined in #4610 (comment). However, it would introduce unsafe
into the codebase for its implementation.
Alternatives
It's possible that I'm simply wrong about enum handling (If that's the case, great!). I've also experimented with mitigating this by:
- Wrapping Report to create the guarantees and transforms I need -- ends up creating a complex error type exclusive to a single library.
- Writing docs to indicate next-on-stack error type for error enums.
- Avoiding returning
Report<E>
from most functions, wrapping it at the top. Returning plainE
allows for pattern matching, but this puts all backtrace line numbers at the topmost error. It also avoids most the benefits of this library.
Additional context
If this seems viable, maybe as a breaking change or as an alternate form of Report
under a feature flag, I'm happy to work on a PR.