Description
The only way to handle errors in systems right now is to result.unwrap()
them. This is both less ergonomic and panic-ey. Ideally systems could optionally return an anyhow::Result<()>
. It would also be great if developers could define their own error handlers. Maybe some devs want to print their errors whereas others want to panic.
I think there are two approaches we could adopt here:
- Adapt legion to support return types
- Add impls for system fns that return results and handle the Result inside of
into_system()
I think option (2) is the least destructive / most friendly to upstream, but it means that only system fns can handle errors.
If this is implemented, it also makes sense to modify bevy libs to return error types instead of Options for common operations to improve erognomics.
fn some_system(a: Res<A>, b: Res<B>, x: Com<X>, y: Com<Y>) -> Result<()> {
a.do_something_risky()?;
// system logic here
Ok(())
}
// inside into_system() impl for Fn(Res<A>, Com<X>) -> Result<()>
system_builder.with_resource::<ErrorHandler>()
// resources are (error_handler, a, b)
result = run_system(a, b, x, y);
error_handler.handle(result);
The biggest downside to implementing this I can think of is that this multiplies the number of system fn impls by 2 (which is already in the hundreds of impls). That would come at an estimated clean-compile time cost of 40 seconds on fast computers ... not ideal. The best way to mitigate that is to revert to non-flat system fn impls, which would then only require a single new impl:
Doing so would both remove the need for a macro for system fn impls and reduce clean compile times by 40 seconds (current) / 80 seconds (with Result impls). But its also not nearly as nice to look at / type
// pseudocode
impl IntoSystem for Fn(ResourceSet, View<'a>) -> Result<()> { /* impl here */ }
fn some_system((a, b): (Res<A>, Res<B>), (x, y): (Com<X>, Com<Y>)) -> Result<()> {
a.do_something_risky()?;
// system logic here
Ok(())
}