-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
FallibleSystemParam
#8196
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
FallibleSystemParam
#8196
Conversation
@JoJoJet Here is a draft. I'll be working on docs later. |
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.
This is definitely my favorite iteration of fallible system params -- I like how much lower the impact of this PR is, compared to the previous ones. I've scanned through it once, though I'll review it fully later.
All the iteration we've gone through in these PRs tells me that there's a lot of considerations at play here, and that there isn't really an "obviously right" way of doing this. I'm marking this PR as controversial, to make sure we get multiple people signing off on this as a direction.
@JoJoJet Removed the generic error type in favor of individual error types. Also added some docs. I'll add an example later on. Another thing worth considering is whether parts common to both |
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.
Not sure how well this would work in practice, but I have another idea for code reuse. Since every FallibleSystemParam
is also a SystemParam
, couldn't we just do
pub trait FallibleSystemParam: SystemParam {
type Error;
/// # Safety
/// This can only be called if it would be safe to call `<Self as SystemParam>::get_param`.
unsafe fn try_get_param<'w, 's>(...) -> Result<Self::Item<'w, 's>, Self::Error>;
}
Doing it in this way prevents duplication of the init_state
, new_archetype
, and apply
functions, as well as their documentation. It also means FallibleSystemParam
doesn't have to be unsafe, since those safety invariants are already guaranteed by its supertrait.
This would make it slightly more verbose for implementers, since they would have to implement both the get_param
and try_get_param
methods, but I think that's probably worth it. Also, individual implementers should be able to just implement get_param
in terms of try_get_param
.
mode_id: ComponentId, | ||
} | ||
|
||
// SAFETY: The resource state is initialized. |
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.
Since this is an example, I think this safety comment could be a lot more verbose. I think it would be good to explain what the safety invariants are and what they mean, and then to explain how we know the invariants are satisfied.
Co-authored-by: JoJoJet <[email protected]>
Co-authored-by: JoJoJet <[email protected]>
Co-authored-by: JoJoJet <[email protected]>
I think the issue with making New idea: there were issues with making Edit: Nevermind, it still has issues when constraining the error type |
Right, my suggestion is that we invert the relationship since it allows us to simplify the API.
It's a bit unfortunate, but it just makes it slightly more annoying for manual implementers of This is a pretty niche feature, I think that simplifying the model in any way we can is desirable. |
Hmm, I think there is a misunderstanding between us. I see it as the opposite: by requiring While it may be simplifying Bevy's code, I think this make things more complex for end users. I think the best choice for reducing the code inside Bevy is to simply wrap common behavior of the traits in a macro. Alternatively, maybe there could be a Also, I think there is a benefit to putting trait bounds on |
Manual implementers of
That's an option, but consider: every |
Hmm, I still think a macro would be the best choice. I don't think I've heard your objections to that? My concern is that we would be sacrificing (a small amount, but still an amount of) usability just to reduce internal code size. If there was some benefit to the end user, I would be more receptive, but I don't see any. There was so much work done to combine the By putting common stuff in a macro, the code size can be reduced and becomes manageable and it keeps usability. |
@JoJoJet Wrapped that stuff in a macro so you can get a sense of that change. |
Seems to work well enough. You'll need to fix the doc links though, which refer to Also, I have a complaint about the derive macro. If I write this, then each field gets treated like a normal #[derive(SystemParam)]
struct MyParam {
id: Entity,
val: Res<Value>,
} As soon as I specify an error type though, it has an implicit context switch where each field is treated as a fallible param. Fields that aren't fallible have to be explicitly marked as such: #[derive(SystemParam)]
#[system_param(error_type = ResError<Value>]
struct MyParam {
#[system_param(infallible)]
id: Entity,
val: Res<Value>,
} I would prefer it if we remove the context switch entirely -- for consistency, the #[derive(SystemParam)]
#[system_param(error_type = ResError<Value>]
struct MyParam {
id: Entity,
#[system_param(try)]
val: Res<Value>,
} I prefer this since it:
|
Also, I want to note that the |
I think the issue of opting in to fallibility instead of opting out is that if you use something that isn't fallible and forget to opt-out, you get a compile error, while if you use something that is fallible and forget to opt-in, you get a runtime error. While it would be nice to show the control flow, I think keeping the way it is prevents unintended panics. |
Ah true, that's an unfortunate complication. |
Got around to fixing those issues (the doc links and error type parsing). |
This has been superceded by the recently merged #18766. |
Objective
Currently, there is no way to implement
SystemParam
forOption<T>
andResult<T, E>
due to orphan rules.Solution
This PR introduces
FallibleSystemParam
, which, when implemented, gives blanket implementations ofSystemParam
forOption<T>
andResult<T, E>
.Compare this to #7162, which introduces
OptionalSystemParam
andResultfulSystemParam
. Here, the design is far simpler and using a type as justT
gives a custom panic message. The downside is that there is less control (you have to provide an error type, you can't just implementOption<T>
), however, this may be desirable since it keeps code more uniform.