diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index 5716497667f43..d3d233da3d547 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -732,7 +732,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { /// - [`for_each_mut`](Self::for_each_mut) to operate on mutable query items. /// - [`iter`](Self::iter) for the iterator based alternative. #[inline] - pub fn for_each<'this>(&'this self, f: impl FnMut(ROQueryItem<'this, Q>)) { + pub fn for_each(&self, f: impl for<'item> FnMut(ROQueryItem<'item, Q>)) { // SAFETY: // - `self.world` has permission to access the required components. // - The query is read-only, so it can be aliased even if it was originally mutable. @@ -771,7 +771,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { /// - [`for_each`](Self::for_each) to operate on read-only query items. /// - [`iter_mut`](Self::iter_mut) for the iterator based alternative. #[inline] - pub fn for_each_mut<'a>(&'a mut self, f: impl FnMut(Q::Item<'a>)) { + pub fn for_each_mut(&mut self, f: impl for<'item> FnMut(Q::Item<'item>)) { // SAFETY: `self.world` has permission to access the required components. unsafe { self.state @@ -779,6 +779,67 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { }; } + /// Runs `f` on each read-only combination of query items of length `K`. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Component)] + /// # struct ComponentA; + /// # + /// fn some_system(query: Query<&ComponentA>) { + /// query.for_each_combinations(|[a1, a2]| { + /// // ... + /// }); + /// } + /// ``` + /// + /// # See also + /// + /// - [`iter_combinations`](Self::iter_combinations) for query item combinations. + /// - [`for_each`](Self::for_each) to operate on query items. + /// - [`iter`](Self::iter) for the iterator based alternative. + #[inline] + pub fn for_each_combinations( + &self, + f: impl for<'item> FnMut([<::ReadOnly as WorldQuery>::Item<'item>; K]), + ) { + self.iter_combinations().for_each(f); + } + + /// Runs `f` on each mutable combination of query items of length `K`. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Component)] + /// # struct ComponentA; + /// # + /// fn some_system(mut query: Query<&mut ComponentA>) { + /// query.for_each_combinations_mut(|[mut a1, mut a2]| { + /// // ... + /// }); + /// } + /// ``` + /// + /// # See also + /// + /// - [`iter_combinations_mut`](Self::iter_combinations_mut) for mutable query item combinations. + /// - [`for_each_mut`](Self::for_each_mut) to operate on mutable query items. + /// - [`iter`](Self::iter) for the iterator based alternative. + #[inline] + pub fn for_each_combinations_mut( + &mut self, + mut f: impl for<'item> FnMut([Q::Item<'item>; K]), + ) { + let mut combinations = self.iter_combinations_mut(); + while let Some(combination) = combinations.fetch_next() { + f(combination); + } + } + /// Returns a parallel iterator over the query results for the given [`World`]. /// /// This can only be called for read-only queries, see [`par_iter_mut`] for write-queries. diff --git a/crates/bevy_ecs_compile_fail_tests/tests/ui/query_lifetime_safety.stderr b/crates/bevy_ecs_compile_fail_tests/tests/ui/query_lifetime_safety.stderr index 7b1d0fe610e9c..5803013abaca7 100644 --- a/crates/bevy_ecs_compile_fail_tests/tests/ui/query_lifetime_safety.stderr +++ b/crates/bevy_ecs_compile_fail_tests/tests/ui/query_lifetime_safety.stderr @@ -1,3 +1,47 @@ +error[E0521]: borrowed data escapes outside of closure + --> tests/ui/query_lifetime_safety.rs:78:35 + | +76 | let mut opt_data: Option<&Foo> = None; + | ------------ `opt_data` declared here, outside of the closure body +77 | let mut opt_data_2: Option> = None; +78 | query.for_each(|data| opt_data = Some(data)); + | ---- ^^^^^^^^^^^^^^^^^^^^^ `data` escapes the closure body here + | | + | `data` is a reference that is only valid in the closure body + +error[E0521]: borrowed data escapes outside of closure + --> tests/ui/query_lifetime_safety.rs:79:39 + | +77 | let mut opt_data_2: Option> = None; + | -------------- `opt_data_2` declared here, outside of the closure body +78 | query.for_each(|data| opt_data = Some(data)); +79 | query.for_each_mut(|data| opt_data_2 = Some(data)); + | ---- ^^^^^^^^^^^^^^^^^^^^^^^ `data` escapes the closure body here + | | + | `data` is a reference that is only valid in the closure body + +error[E0521]: borrowed data escapes outside of closure + --> tests/ui/query_lifetime_safety.rs:86:39 + | +84 | let mut opt_data_2: Option> = None; + | -------------- `opt_data_2` declared here, outside of the closure body +85 | let mut opt_data: Option<&Foo> = None; +86 | query.for_each_mut(|data| opt_data_2 = Some(data)); + | ---- ^^^^^^^^^^^^^^^^^^^^^^^ `data` escapes the closure body here + | | + | `data` is a reference that is only valid in the closure body + +error[E0521]: borrowed data escapes outside of closure + --> tests/ui/query_lifetime_safety.rs:87:35 + | +85 | let mut opt_data: Option<&Foo> = None; + | ------------ `opt_data` declared here, outside of the closure body +86 | query.for_each_mut(|data| opt_data_2 = Some(data)); +87 | query.for_each(|data| opt_data = Some(data)); + | ---- ^^^^^^^^^^^^^^^^^^^^^ `data` escapes the closure body here + | | + | `data` is a reference that is only valid in the closure body + error[E0502]: cannot borrow `query` as mutable because it is also borrowed as immutable --> tests/ui/query_lifetime_safety.rs:17:39 | @@ -97,23 +141,3 @@ error[E0502]: cannot borrow `query` as immutable because it is also borrowed as | ^^^^^^^^^^^^ immutable borrow occurs here 72 | assert_eq!(data, &mut *data2); // oops UB | ----- mutable borrow later used here - -error[E0502]: cannot borrow `query` as mutable because it is also borrowed as immutable - --> tests/ui/query_lifetime_safety.rs:79:13 - | -78 | query.for_each(|data| opt_data = Some(data)); - | -------------------------------------------- immutable borrow occurs here -79 | query.for_each_mut(|data| opt_data_2 = Some(data)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here -80 | assert_eq!(opt_data.unwrap(), &mut *opt_data_2.unwrap()); // oops UB - | -------- immutable borrow later used here - -error[E0502]: cannot borrow `query` as immutable because it is also borrowed as mutable - --> tests/ui/query_lifetime_safety.rs:87:13 - | -86 | query.for_each_mut(|data| opt_data_2 = Some(data)); - | -------------------------------------------------- mutable borrow occurs here -87 | query.for_each(|data| opt_data = Some(data)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ immutable borrow occurs here -88 | assert_eq!(opt_data.unwrap(), &mut *opt_data_2.unwrap()); // oops UB - | ---------- mutable borrow later used here