-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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 System Parameters #6923
Fallible System Parameters #6923
Conversation
Pros:
Cons:
|
Unresolved Questions:
#[derive(SystemParam)]
#[fallibility(Optional)]
struct MyParam {
...
}
Edit: All my questions were answered below. |
Okay, this is ready to be reviewed and merged. |
@alice-i-cecile Would you like to review this? (I'm not sure on @JoJoJet's progress on his PR, but I would like to get this merged so he can freely rebase) |
Change `Box<dyn Error>` to `&dyn Error`
Okay, I've changed it so |
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.
Definitely on board with the goals here: this abstracts a common and important pattern and allows third-party crates to get in on the fun.
I like the use of a generic with a sealed trait bound to me: I think that's a good way to implement this and IMO the overall comprehensibility of this code is improved. I particularly like how much of the unsafe code is clearer and simpler.
#6919 will make this code much clearer: I'd like to merge that ASAP regardless of what we do here.
Some nits to clean up before merging though: see comments.
Co-authored-by: Alice Cecile <[email protected]>
Yes
No: this is clear and uses common idioms.
Yes, or a good doc comment. It's hard to understand what it's doing here. |
The derive macro would be nice, but I won't block on it. If it doesn't get done in this PR please open an issue! |
Okay, I got the macro done and modified the documentation. |
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'm on board with the optional SystemParam
changes -- it's clearly useful, and necessary for allowing users to bypass the orphan rule (I liked the state that #6854 was in). However, I am very skeptical as to the how useful Resultful
params are.
Here are some of my thoughts
&dyn Error
has the same problem asBox<dyn Error>
, in that you can't do much with it except for log or unwrap it.- Put another way: the only reason you would want
Result
as aSystemParam
is if you can actually handle the error case. It's not really possible to meaningfully handle adyn Error
, so it doesn't make sense to use it as aSystemParam
.
- Put another way: the only reason you would want
- Results are usually returned or bubbled up from a function. Passing a
Result
as an argument looks very questionable -- usually, errors should be handled by the caller instead of passed into a function (in this case, the 'caller' would be theSystemParam
impl for that type). - Having
&dyn Error
as the error type feels quite magical and non-obvious.
Feel free to wait for more opinions, but I don't think Resultful
params should be included.
I think having In defense of
|
Here is a small program demonstrating use bevy::{
ecs::system::{
Infallible, OptionState, Optional, ResState, Resultful, SystemMeta, SystemParam,
SystemParamState,
},
prelude::*,
};
use std::error::Error;
#[derive(Debug, Resource)]
struct SomeResource {
value: u32,
}
#[derive(Debug)]
struct MyParam<'w> {
value: Res<'w, SomeResource>,
}
impl<'w> SystemParam<Resultful> for MyParam<'w> {
type State = MyParamState;
}
struct MyParamState {
res_state: ResState<SomeResource>,
}
unsafe impl SystemParamState<Resultful> for MyParamState {
fn init(world: &mut World, system_meta: &mut bevy::ecs::system::SystemMeta) -> Self {
Self {
res_state: <ResState<SomeResource> as SystemParamState<Optional>>::init(
world,
system_meta,
),
}
}
type Item<'w, 's> = MyParam<'w>;
#[inline]
unsafe fn get_param<'w, 's>(
state: &'s mut Self,
system_meta: &SystemMeta,
world: &'w World,
change_tick: u32,
) -> Result<Self::Item<'w, 's>, &'s dyn Error> {
// Since `Res<T>` doesn't implement `SystemParam<Resultful>`, we have to use `Option<Res<T>>` and map it to a result.
let some_res = <<Res<SomeResource> as SystemParam<Optional>>::State as SystemParamState<
Optional,
>>::get_param(&mut state.res_state, system_meta, world, change_tick);
match some_res {
Some(r) => Ok(MyParam { value: r }),
None => Err(&MyError(45)),
}
}
}
#[derive(Debug)]
struct MyError(u32);
impl std::fmt::Display for MyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "MyError has {}!", self.0)
}
}
impl Error for MyError {}
#[derive(SystemParam, Debug)]
#[fallibility(Resultful)]
struct MySpecialParam<'w> {
param: MyParam<'w>,
}
fn main() {
App::new()
.insert_resource(SomeResource { value: 653 })
.add_system(foo)
.run();
}
fn foo(foo: Option<MySpecialParam>) {
println!("{foo:?}");
} |
You can do complex error handling already, you just can't use std's Also, using
We shouldn't erase type information for the sake of making the derive macro easier to write. Also, the derive macro is too restrictive to be very useful right now. #[derive(SystemParam)]
#[fallibility(Resultful)]
struct Ouch<'w, 's> {
r: Res<'w, R>, // error[E0277]: the trait bound `Res<'__w, R>: SystemParam<Resultful>` is not satisfied
fallible: MyFallibleParam<'w, 's>,
} ...I hope I'm not coming across as combative, I'm just trying to help give feedback :) |
Ah, that's right. That's not really nice either IMO, but I guess it works.
Hmm, good point. I'll look into this.
No, I was just making the point that it isn't entirely bad.
Yeah, I guess I can add a field level attribute to override how it's handled.
Of course, thanks. I agree, |
Now, say we were able to use |
Hmm, so I thought using a generic trait would be a good idea to keep the code base DRY, but it makes implementing |
Objective
There is no way to implement
SystemParam
forOption<T>
andResult<T, E>
in downstream crates because of orphan rules.Solution
Make
SystemParam
generic forT: Fallibility
. The sealed traitFallibility
describes the return value ofSystemParamState::get_param
via the generic associated typeScope<T>
. Users can then use the marker structsInfallible
,Optional
, andResultful
, which defineScope<T>
to beT
,Option<T>
andResult<T, &dyn Error>
.&dyn Error
was chosen because there was no way to constrain the type parameterE
(this would require higher-ranked types).The point of doing it this way instead of creating
OptionalSystemParam
andResultfulSystemParam
traits is that there would otherwise be significant overlap between traits asSystemParamFetch
andSystemParamState
get merged intoSystemParam
.Changelog
SystemParam
toSystemParam<T: Fallibility>
.SystemParamState
toSystemParamState<T: Fallibility>
.SystemParam
toSystemParam<Infallible>
orSystemParam<Optional>
, when appropriate.SystemParamState
toSystemParamState<Infallible>
orSystemParamState<Optional>
, when appropriate.Fallibility
trait.Infallible
,Optional
, andResultful
markers.Migration Guide
SystemParam
andSystemParamState
have to be changed to their respective generic type.