Add context messages to BevyError#24528
Conversation
Add a `context` method to `Result` and `Option` and some extra utilities to `ResultSeverityExt`
|
It looks like your PR has been selected for a highlight in the next release blog post, but you didn't provide a release note. Please review the instructions for writing release notes, then expand or revise the content in the release notes directory to showcase your changes. |
|
Thank you for working on this! This should close #19714. |
SpecificProtagonist
left a comment
There was a problem hiding this comment.
Generally in favor of both the severity shorthands and the error context, though they could be done in separate PRs because they're independent. The fix to backtrace filtering also shouldn't be part of this PR.
| } | ||
| } | ||
|
|
||
| impl<T> ContextExt<T> for Option<T> { |
There was a problem hiding this comment.
This seems odd to me and might be a bit confusing to use. Attaching a context to an option turns the context into an error? And options work with .with_context but not with .with_severity (as map_severity can't work with options)?
There was a problem hiding this comment.
having an Option that's None usually means something failed, i.e. getting an element from an array, so you can attach a context message to it to turn it into an error that can be displayed. anyhow does the same thing so i just copied it from there really :p
having with_severity on an Option means that it'll need to turn into an error with some message, but if you then use context on it now you have 2 messages (i.e. some context message: option is none), which is different from if you first used context and then with_severity (you'll just get some context message)
There was a problem hiding this comment.
This is odd to me too.
What you describe can be done with Option::ok_or and Option::ok_or_else:
let value = option.ok_or_else(|| format!("thing"))?;And the with_context documentation suggests you are adding context to an existing result. That is not what is being done here.
There was a problem hiding this comment.
yeah fair, i just added it to Option because anyhow has it for Option as well, so i imagine people might expect it to be there for BevyError too
Agreed, I would prefer this split into three :) |
BevyError ergonomicsBevyError
|
fair! i've split the shorthands into #24555 and got rid of the backtrace changes for now |
kfc35
left a comment
There was a problem hiding this comment.
This looks good to me. The release note is easy to understand as well.
NthTensor
left a comment
There was a problem hiding this comment.
Looks good. I am, separately, interested in the idea of attaching "hints" or "help messages" to errors.
MarcGuiselin
left a comment
There was a problem hiding this comment.
Looks good overall, but I want to challenge the design here which assumes several things:
- There will only ever be one error
- That error will have a sequence of causes, or context
- Context is always a string
anyhow makes no such assumptions. It implements context internally via a ContextError:
pub(crate) struct ContextError<C, E> {
pub context: C,
pub error: E,
}A context in anyhow can be understood as a branching tree of errors + context starting at the latest context.
Given this builder: Err(A4).context("B3").with_context(|| Err(C2).context("D1").unwrap_err())
You get an error that looks something like this:
ContextError {
context: ContextError {
context: "D1",
error: C2,
},
error: ContextError {
context: "B3",
error: A4,
}
}
Anyhow can correctly implement Display and Debug on its Error type and all children recursively:
- For
Display/ToStringyou get the general error and context chain printed out - For
Debugyou get full contextual information of all errors and contexts [Formatter::alternate](https://doc.rust-lang.org/std/fmt/struct.Formatter.html#method.alternate)is handled correctly
We can also introspect the error/context and check whether it is the exact one we expect. This is very useful for tests or for recovering from errors.
| } | ||
| } | ||
|
|
||
| impl<T> ContextExt<T> for Option<T> { |
There was a problem hiding this comment.
This is odd to me too.
What you describe can be done with Option::ok_or and Option::ok_or_else:
let value = option.ok_or_else(|| format!("thing"))?;And the with_context documentation suggests you are adding context to an existing result. That is not what is being done here.
Head branch was pushed to by a user without write access
i'm not sure that's a very common usecase, i certainly haven't seen a context be another error, especially not one with a context (which would be formatted a bit oddly i'm pretty sure) |
I agree, I'm using an extreme use-case to illustrate how anyhow maintains the shape of the errors and the context. If bevy errors are going to contain similar functionality, we must acknowledge how our model compares to anyhow and what we loose in the process, such as the original shape of the accumulated errors and context. Maybe that's fine, especially for a first version :)
In anyhow, a context is essentially a Another benefit of this is that you are only lazily evaluating the context when it needs to be displayed instead of always formatting and producing a string when there is an error, since sometimes the whole context might not be needed. |
Objective
Add a
context()extension method toResult<T>andOption<T>likeanyhowSolution
Add a
Vec<String>field toInnerBevyErrorto store context messages added viacontext()Fixes #19714.
Testing
Added the
contextunit test to test messages produced bycontext()and acontext_downcastingunit test to test that downcasting still works when using.context