-
Notifications
You must be signed in to change notification settings - Fork 83
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
GEN-192: Use Pin API for error-stack
#5008
Comments
Hi @Bennett-Petzold and thanks for the issue. I don't think I really can follow your issue. What is the issue around the first snippet?
I don't see a reason why this should not work. |
error-stack
I've written up an expanded example demonstrating why the first form is incompatible with full error stack output. The first code version is with regular enum nesting, and the second version is the changes to properly track lines with use error_stack::Report;
use thiserror::Error;
fn main() {
foo().unwrap();
}
#[derive(Debug, Error)]
#[error("error 1!")]
struct Err1 {}
#[derive(Debug, Error)]
enum Err2 {
#[error("error 2 caused by error 1!")]
Downstream(#[source] Err1),
#[error("error 2!")]
Originating,
}
fn bar() -> Result<(), Err1> {
Err(Err1 {})
}
fn foo() -> Result<(), Report<Err2>> {
let orig_err = bar();
match orig_err {
Ok(()) => Ok(()),
Err(e) => Err(Err2::Downstream(e).into()),
}
} This produces
The backtrace puts all error line numbers at line 28, where the Report is created. If I change my type to the second version, with this diff... 15c15
< Downstream(#[source] Err1),
---
> Downstream,
20,21c20,21
< fn bar() -> Result<(), Err1> {
< Err(Err1 {})
---
> fn bar() -> Result<(), Report<Err1>> {
> Err(Err1 {}.into())
28c28
< Err(e) => Err(Err2::Downstream(e).into()),
---
> Err(e) => Err(e.change_context(Err2::Downstream)), It produces a backtrace with all line number tracking down to the actual source sites.
Here's the cargo project with both versions: report_diff.tgz |
Hmm, I see your point, thanks for providing the example. I'm a bit hesitant about the amount of Generally, I don't have a strong opinion around how we mutate the data. Direct mutation is nice, but a proxy object is good as well. The latter would even allow to create immutable reports. A breaking change here is not the end of the world as long as migrating is easy. We are still in pre-release because (a) a few features we rely on are not stabilized, yet, and (b) I expect changes around the generics and multi-sources, which will be a breaking change as well. I have not tested around with your provided code, yet, and maybe I'm missing something, but what prevents us from creating |
I realized it can be done entirely safely with
Safe implementationUse ... // (in frame/mod.rs)
// mutation is now replace-only, via "frame.get_mut().set(VALUE)"
// VALUE is whatever we're holding as "dyn FrameImpl".
frame: RefCell<Pin<Box<dyn FrameImpl>>>,
...
struct ReportCell<T>(RefCell<Pin<Box<T>>>);
impl Deref<T> for ReportCell<T> {
type Target = T;
/// Desugared lifetimes for clarity.
/// The pointer is bound to the lifetime of ReportCell.
/// ReportCell is only constructed in Report, only borrows valid lower data data in Report,
/// and cannot be copied out of Report.
/// ReportCell is only obtained via a Report borrow, so the lifetimes bind as:
/// &'r Report -> &'a ReportCell -> &'a T (where 'r outlives 'a)
/// Report then must be immutably borrowed for the lifetime of this return.
fn deref<'a>(&'a self) -> &'a T {
self.0.borrow().map(|cell| cell.as_ref())
}
}
fn from_ref(existing: Report<Err1>) -> Report<Err2>> {
existing.change_context(Err2::Downstream(existing.current_context_cell().clone()))
} Justification for
|
On further reflection, the |
Thank you for the detailed response! That would all result in a lot of pointer indirection (both, the I'd prefer to discover this route before we largely overhaul the internal representation of |
I don't think the unsafe Pin compiles down to more pointer indirection (it's a complicated way of preventing the compiler from catching values), but one pointer deref a non-panic handling path isn't much cheaper than two pointer derefs anyways. You're right it comes at a perf hit. I think I'm fighting some architectural differences with the idea of wrapping the Errors at all -- Rework around |
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.
However, this does not seem possible in
Report
. I believe the way to emulate this for functions producing reports is as follows: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 asBox<dyn FrameImpl>
with mutable handle methods. Make this typePin<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, enablingDeref
access through someconst *
wrapper (ReportCell
) and a special construction method (change_context_ref
) that provides a reference to the current context.For soundness when a
Context
uses borrowed data during drop,Report
would need to be changed fromBox<Vec<Frame>>
toBox<Vec<ManuallyDrop<Frame>>>
and given a customDrop
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:
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.The text was updated successfully, but these errors were encountered: