You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
alice-i-cecile opened this issue
Sep 14, 2023
· 2 comments
Labels
A-ECSEntities, components, systems, and eventsC-BugAn unexpected or incorrect behaviorD-ComplexQuite challenging from either a design or technical perspective. Ask for help!P-HighThis is particularly urgent, and deserves immediate attention
impl<Q:WorldQuery + 'static,F:ROWorldQuery + 'static>SystemParamforQuery<'_,'_,Q,F>{// Actual code}
By contrast, no equivalent bound exists on WorldQuery.
The net effect of this is that:
fnfoo<'a>(){let _:Query<&'a Transform>;}
compiles, since &'a Transform implements WorldQuery, no matter what the actual lifetime 'a is.
However, this is not a valid SystemParam, unless that lifetime is actually static (aka 'a: 'static).
Why this matters
So, we've seen users run into pain here within Bevy: see #7447 and #8192. Avoiding this footgun would be inherently good!
But the much larger problem comes when Rust wants to improve how implied bounds are computed. This, in a truly unprecedented fashion, breaks Bevy and effectively nothing else, because of the extremely normal things we do to the type system.
To explain their proposed changes:
When writing Rust programs, you don't always have to explicitly write out the exact lifetimes that are needed.
Instead, the compiler can sometimes infer what lifetimes must exist in order for your program to function: this is called "implied bounds".
However, the current approach to doing this is pretty ad-hoc, and underspecified.
In particular, the existing design uses trait solver flavored logic to do this in some cases (which Bevy hits) by examining the trait impls used.
This is both sketchy, and is in conflict with some more sensible implied bounds work that is currently missing.
So they want to change how it works!
The consequence of that change (if both Bevy and rustc hold their courses) is that Bevy users will get a very confusing, un-silenceable and unactionable lint (at times). In the future, this would be on the path to become a true compiler error.
What times? Well, @BoxyUwU did some digging, and discovered that the lint fires in exactly two places in our code base: both on our two internal uses of ParamSet. Experimenting more, this triggers on any use of ParamSet that involves queries with a Q or F type that have lifetimes that are not known to be static. In Bevy, that means &T and &mut T` query types.
The reason for this gets back to the problem at the top of this issue. Somewhere, we're currently relying on these implied bounds to effectively transfer those 'static lifetime requirements down into the Query, via the power of trait magic. So when rustc stops relying on trait information for implied bounds, generic types that combine WorldQuery and SystemParam with unconstrained lifetimes fall afoul of the new rules.
How can we fix this?
As @BoxyUwU and I see it, there are two fundamental approaches by which we could fix this.
Make WorldQuery more 'static, by adding bounds everywhere.
Make WorldQuery's SystemParam impl not require 'static
Either way, the discrepancy disappears, and we stop relying on implied bounds from trait impls to get the two parts to play nice.
Approach 1 is likely to improve end user ergonomics when working with custom SystemParam. There's a small chance that it regresses ergonomics in end user code (very bad!), by for example requiring users to write out &'static Transform in their queries. It also feels "more correct": using non static lifetimes in WorldQuery types doesn't seem to ever be correct: we're just type-punning with references.
This also may not work without help from rustc: trait WorldQuery: 'static in combination with Query<Q: WorldQuery, F: WorldQuery>should imply that Q and F are always 'static, but it's not clear that it currently does.
Approach 2 would be nicely targeted, and bring the impl into line with other implementations of SystemParam and how we implement WorldQuery (none of which requires 'static). However, it may not work, or require complex unsafe code to get working.
How do I test if my fix worked?
You will need to:
Create a branch of Bevy with your proposed fix.
Get the correct version of rustc, with the proposed PR included.
Set up your rustup toolchain so then it links to your local rustc build, following the contributing guide below.
@lcnr warns me that this PR is somewhat stale (from May 2023), and should probably be rebased. If that happens, you'll want to test with the rebased version instead to get more accurate results.
The text was updated successfully, but these errors were encountered:
alice-i-cecile
added
C-Bug
An unexpected or incorrect behavior
A-ECS
Entities, components, systems, and events
P-High
This is particularly urgent, and deserves immediate attention
D-Complex
Quite challenging from either a design or technical perspective. Ask for help!
labels
Sep 14, 2023
As a small update, rust-lang/rust#118553 has landed stable Rust 1.77. If there were any uncaught errors from nightly users, they will likely surface now.
A-ECSEntities, components, systems, and eventsC-BugAn unexpected or incorrect behaviorD-ComplexQuite challenging from either a design or technical perspective. Ask for help!P-HighThis is particularly urgent, and deserves immediate attention
The problem
Currently,
SystemParam
is only implemented forQuery
types that are'static
: these types cannot contain any temporary references.The key code is:
By contrast, no equivalent bound exists on
WorldQuery
.The net effect of this is that:
compiles, since
&'a Transform
implementsWorldQuery
, no matter what the actual lifetime'a
is.However, this is not a valid
SystemParam
, unless that lifetime is actually static (aka'a: 'static
).Why this matters
So, we've seen users run into pain here within Bevy: see #7447 and #8192. Avoiding this footgun would be inherently good!
But the much larger problem comes when Rust wants to improve how implied bounds are computed. This, in a truly unprecedented fashion, breaks Bevy and effectively nothing else, because of the extremely normal things we do to the type system.
To explain their proposed changes:
The consequence of that change (if both Bevy and rustc hold their courses) is that Bevy users will get a very confusing, un-silenceable and unactionable lint (at times). In the future, this would be on the path to become a true compiler error.
What times? Well, @BoxyUwU did some digging, and discovered that the lint fires in exactly two places in our code base: both on our two internal uses of
ParamSet
. Experimenting more, this triggers on any use ofParamSet
that involves queries with aQ
orF
type that have lifetimes that are not known to be static. In Bevy, that means&T
and &mut T` query types.The reason for this gets back to the problem at the top of this issue. Somewhere, we're currently relying on these implied bounds to effectively transfer those
'static
lifetime requirements down into theQuery
, via the power of trait magic. So whenrustc
stops relying on trait information for implied bounds, generic types that combineWorldQuery
andSystemParam
with unconstrained lifetimes fall afoul of the new rules.How can we fix this?
As @BoxyUwU and I see it, there are two fundamental approaches by which we could fix this.
WorldQuery
more 'static
, by adding bounds everywhere.WorldQuery
'sSystemParam
impl not require 'staticEither way, the discrepancy disappears, and we stop relying on implied bounds from trait impls to get the two parts to play nice.
Approach 1 is likely to improve end user ergonomics when working with custom
SystemParam
. There's a small chance that it regresses ergonomics in end user code (very bad!), by for example requiring users to write out&'static Transform
in their queries. It also feels "more correct": using non static lifetimes inWorldQuery
types doesn't seem to ever be correct: we're just type-punning with references.This also may not work without help from
rustc
:trait WorldQuery: 'static
in combination withQuery<Q: WorldQuery, F: WorldQuery>
should imply thatQ
andF
are always'static
, but it's not clear that it currently does.Approach 2 would be nicely targeted, and bring the impl into line with other implementations of
SystemParam
and how we implementWorldQuery
(none of which requires'static
). However, it may not work, or require complex unsafe code to get working.How do I test if my fix worked?
You will need to:
rustc
, with the proposed PR included.cargo +stage1 build
.To get the correct version of
rustc
, follow their contributing guide. Alternatively, you may be able to pull in a cached version more easily using[rustup-toolchain-install-master] (https://github.com/kennytm/rustup-toolchain-install-master).@lcnr warns me that this PR is somewhat stale (from May 2023), and should probably be rebased. If that happens, you'll want to test with the rebased version instead to get more accurate results.
The text was updated successfully, but these errors were encountered: