-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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
Fallible systems #16589
Fallible systems #16589
Conversation
Even without the actual error handling benefits this provides, just having a more blessed way to use |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree this probably needs an example, but I like the approach. Opens up the possibility of having error handlers in the future, which would resolve the to-panic or not to-panic debate entirely. This also lays the groundwork for how fallibility in Command
s could work. Really nice work!
It was mentioned in Discord, but I'll include it here for posterity: with fallible systems getting first-class treatment, there may be room to consider removing the panicking variants of certain functions (e.g., |
Co-authored-by: Zachary Harrold <zac@harrold.com.au>
Co-authored-by: Zachary Harrold <zac@harrold.com.au>
Co-authored-by: Zachary Harrold <zac@harrold.com.au>
That's in line with the third point Cart proposed in #14275 (comment). He indicated then that it was important to land all the related changes in a single release cycle, and I agree. This PR provides his (1), what (2) and (3) look like is up to @alice-i-cecile and the other designated ecs experts. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Amazing to see how straightforward this is, all things considered. Very excited!
Why? This just makes the code more jarring compared to the rest of the Rust ecosystem, and more cognitive load to switch between returning nothing vs. returning some value. It'd make sense if it's something useful like https://docs.rs/anyhow/latest/anyhow/fn.Ok.html |
In this I am trying to defer to my understanding of Cart's preferences. He uses a const in the linked issue, and I believe has expressed that |
@alice-i-cecile requesting removal of the |
Just like migration guides, we should keep this label around even after they're written for searchability and tooling. We might wan to rename that to be more clear though 🤔 |
)); | ||
|
||
// Create a new sphere mesh: | ||
let mut sphere_mesh = Sphere::new(1.0).mesh().ico(7)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What a sight to behold. Once proper (user configurable) handlers are added in a follow-up this will be perfect. Bevy APIs can be simplified and made more reliable without any loss in ergonomics (IMO). Adding Ok(())
at the end of a system is a small price to pay that (hopefully) Rust will solve on its own (since the issue isn't specific to Bevy)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah :) this example is really just a minimal placeholder. Once we have handlers hooked up, I intend to go through and update all the examples to use this style (where it makes sense).
|
Alright, I added the most basic tests and examples in the world. There will be more to do there when handlers are hooked up. Ready for review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nth this is good to go once it's merge-conflict free. I do prefer the "everything is fallible" approach by WrongShoe, but that's easily left to a follow-up refactor. Let's get the ball rolling here.
Objective
Error handling in bevy is hard. See for reference #11562, #10874 and #12660. The goal of this PR is to make it better, by allowing users to optionally return
Result
from systems as outlined by Cart in #14275 (comment).Solution
This PR introduces a new
ScheuleSystem
type to represent systems that can be added to schedules. Instances of this type contain either an infallibleBoxedSystem<(), ()>
or a fallibleBoxedSystem<(), Result>
.ScheuleSystem
implementsSystem<In = (), Out = Result>
and replaces all uses ofBoxedSystem
in schedules. The async executor now receives a result after executing a system, which for infallible systems is alwaysOk(())
. Currently it ignores this result, but more useful error handling could also be implemented.Aliases for
Error
andResult
have been added to thebevy_ecs
prelude, as well as constOK
which new users may find more friendly thanOk(())
.Testing
Showcase
The following minimal example prints "hello world" once, then completes.
Migration Guide
This change should be pretty much non-breaking, except for users who have implemented their own custom executors. Those users should use
ScheduleSystem
in place ofBoxedSystem<(), ()>
and import theSystem
trait where needed. They can choose to do whatever they wish with the result.Current Work
Draft Release Notes
As of this release, systems can now return results.
First a bit of background: Bevy has hisotrically expected systems to return the empty type
()
. While this makes sense in the context of the ecs, it's at odds with how error handling is typically done in rust: returningResult::Error
to indicate failure, and using the short-circuiting?
operator to propagate that error up the call stack to where it can be properly handled. Users of functional languages will tell you this is called "monadic error handling".Not being able to return
Results
from systems left bevy users with a quandry. They could add custom error handling logic to every system, or manually pipe every system into an error handler, or perhaps sidestep the issue with some combination of fallible assignents, logging, macros, and early returns. Often, users would just litter their systems with unwraps and possible panics.While any one of these approaches might be fine for a particular user, each of them has their own drawbacks, and none makes good use of the language. Serious issues could also arrise when two different crates used by the same project made different choices about error handling.
Now, by returning results, systems can defer error handling to the application itself. It looks like this:
There are currently some limitations. Systems must either return
()
orResult<(), Box<dyn Error + Send + Sync + 'static>>
, with no in-between. Results are also ignored by default, and though implementing a custom handler is possible, it involves writing your own custom ecs executor (which is not recomended).Systems should return errors when they cannot perform their normal behavior. In turn, errors returned to the executor while running the schedule will (eventually) be treated as unexpected. Users and library authors should prefer to return errors for anything that disrupts the normal expected behavior of a system, and should only handle expected cases internally.
We have big plans for improving error handling further: