From 537193dc3e57eb5fc440d76a5baaa6dc5b632262 Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Wed, 12 Nov 2025 19:41:05 -0500 Subject: [PATCH 1/7] refactor: use `NonNull::without_provenance()` in `dangling_with_align()` --- crates/bevy_ptr/src/lib.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/crates/bevy_ptr/src/lib.rs b/crates/bevy_ptr/src/lib.rs index 99cd38b6ab4ca..6fcb1bf29d401 100644 --- a/crates/bevy_ptr/src/lib.rs +++ b/crates/bevy_ptr/src/lib.rs @@ -1103,13 +1103,17 @@ impl<'a, T> From<&'a [T]> for ThinSlicePtr<'a, T> { } /// Creates a dangling pointer with specified alignment. -/// See [`NonNull::dangling`]. +/// +/// This is an untyped version of [`NonNull::dangling()`]. The returned pointer has no +/// [provenance](https://doc.rust-lang.org/stable/std/ptr/index.html#provenance). +/// +/// # Panics +/// +/// If `align` is not a power of two (2, 4, 8, 16, etc.). pub const fn dangling_with_align(align: NonZeroUsize) -> NonNull { debug_assert!(align.is_power_of_two(), "Alignment must be power of two."); - // SAFETY: The pointer will not be null, since it was created - // from the address of a `NonZero`. - // TODO: use https://doc.rust-lang.org/std/ptr/struct.NonNull.html#method.with_addr once stabilized - unsafe { NonNull::new_unchecked(ptr::null_mut::().wrapping_add(align.get())) } + + NonNull::without_provenance(align) } mod private { From 11b8c447b69724d0afc01a1f3024b8d6329fdf1c Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Wed, 12 Nov 2025 20:58:16 -0500 Subject: [PATCH 2/7] chore: bump msrv to 1.89.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 40989da9fd7d2..a9cd5d18807f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ keywords = ["game", "engine", "gamedev", "graphics", "bevy"] license = "MIT OR Apache-2.0" repository = "https://github.com/bevyengine/bevy" documentation = "https://docs.rs/bevy" -rust-version = "1.88.0" +rust-version = "1.89.0" [workspace] resolver = "2" From 612fd5dafd1e11897c8c5afe3db8e6101ca584ea Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Thu, 13 Nov 2025 20:57:11 -0500 Subject: [PATCH 3/7] refactor!: remove `dangling_with_align()` and inline it into its call site `dangling_with_align()` is only being used once, in `bevy_ecs`, and it's only 2 lines of code! Let's squash it :) --- crates/bevy_ecs/Cargo.toml | 2 +- crates/bevy_ecs/src/storage/blob_array.rs | 5 ++++- crates/bevy_ptr/src/lib.rs | 15 --------------- 3 files changed, 5 insertions(+), 17 deletions(-) diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index 98b9c33bb07c8..14da777c6900d 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -8,7 +8,7 @@ repository = "https://github.com/bevyengine/bevy" license = "MIT OR Apache-2.0" keywords = ["ecs", "game", "bevy"] categories = ["game-engines", "data-structures"] -rust-version = "1.86.0" +rust-version = "1.89.0" [features] default = ["std", "bevy_reflect", "async_executor", "backtrace"] diff --git a/crates/bevy_ecs/src/storage/blob_array.rs b/crates/bevy_ecs/src/storage/blob_array.rs index 3ef2e5a90d007..bab0aad03c443 100644 --- a/crates/bevy_ecs/src/storage/blob_array.rs +++ b/crates/bevy_ecs/src/storage/blob_array.rs @@ -42,7 +42,10 @@ impl BlobArray { ) -> Self { if capacity == 0 { let align = NonZeroUsize::new(item_layout.align()).expect("alignment must be > 0"); - let data = bevy_ptr::dangling_with_align(align); + + // Create a dangling pointer with the given alignment. + let data = NonNull::without_provenance(align); + Self { item_layout, drop: drop_fn, diff --git a/crates/bevy_ptr/src/lib.rs b/crates/bevy_ptr/src/lib.rs index 6fcb1bf29d401..322779ca8e5dd 100644 --- a/crates/bevy_ptr/src/lib.rs +++ b/crates/bevy_ptr/src/lib.rs @@ -12,7 +12,6 @@ use core::{ fmt::{self, Debug, Formatter, Pointer}, marker::PhantomData, mem::{self, ManuallyDrop, MaybeUninit}, - num::NonZeroUsize, ops::{Deref, DerefMut}, ptr::{self, NonNull}, }; @@ -1102,20 +1101,6 @@ impl<'a, T> From<&'a [T]> for ThinSlicePtr<'a, T> { } } -/// Creates a dangling pointer with specified alignment. -/// -/// This is an untyped version of [`NonNull::dangling()`]. The returned pointer has no -/// [provenance](https://doc.rust-lang.org/stable/std/ptr/index.html#provenance). -/// -/// # Panics -/// -/// If `align` is not a power of two (2, 4, 8, 16, etc.). -pub const fn dangling_with_align(align: NonZeroUsize) -> NonNull { - debug_assert!(align.is_power_of_two(), "Alignment must be power of two."); - - NonNull::without_provenance(align) -} - mod private { use core::cell::UnsafeCell; From 79202176672e2278e05a0b881d48a5c25bc68809 Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Thu, 13 Nov 2025 21:05:02 -0500 Subject: [PATCH 4/7] chore: write migration guide --- .../migration-guides/remove_dangling_with_align.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 release-content/migration-guides/remove_dangling_with_align.md diff --git a/release-content/migration-guides/remove_dangling_with_align.md b/release-content/migration-guides/remove_dangling_with_align.md new file mode 100644 index 0000000000000..71fbd10e859c9 --- /dev/null +++ b/release-content/migration-guides/remove_dangling_with_align.md @@ -0,0 +1,14 @@ +--- +title: Remove `bevy::ptr::dangling_with_align()` +pull_requests: [21822] +--- + +`bevy::ptr::dangling_with_align()` has been removed. Use `NonNull::without_provenance()` instead: + +```rust +// 0.17 +let ptr = dangling_with_align(align); + +// 0.18 +let ptr = NonNull::without_provenance(align); +``` From 8f10a9950e5203b48d2b6c74e8418c89745754bf Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Thu, 13 Nov 2025 21:23:40 -0500 Subject: [PATCH 5/7] fix: new clippy lints --- crates/bevy_ecs/src/component/clone.rs | 20 +++++----- crates/bevy_ecs/src/entity/clone_entities.rs | 10 ++--- crates/bevy_ecs/src/observer/mod.rs | 24 ++++++------ crates/bevy_ecs/src/relationship/mod.rs | 41 ++++++++++---------- crates/bevy_ecs/src/system/system.rs | 8 ++-- 5 files changed, 50 insertions(+), 53 deletions(-) diff --git a/crates/bevy_ecs/src/component/clone.rs b/crates/bevy_ecs/src/component/clone.rs index 3a3784350e36a..a645463923bb6 100644 --- a/crates/bevy_ecs/src/component/clone.rs +++ b/crates/bevy_ecs/src/component/clone.rs @@ -110,20 +110,18 @@ pub fn component_clone_via_reflect(source: &SourceComponent, ctx: &mut Component // Try to clone using ReflectFromReflect if let Some(reflect_from_reflect) = registry.get_type_data::(type_id) - { - if let Some(mut component) = + && let Some(mut component) = reflect_from_reflect.from_reflect(source_component_reflect.as_partial_reflect()) + { + if let Some(reflect_component) = + registry.get_type_data::(type_id) { - if let Some(reflect_component) = - registry.get_type_data::(type_id) - { - reflect_component.map_entities(&mut *component, ctx.entity_mapper()); - } - drop(registry); - - ctx.write_target_component_reflect(component); - return; + reflect_component.map_entities(&mut *component, ctx.entity_mapper()); } + drop(registry); + + ctx.write_target_component_reflect(component); + return; } // Else, try to clone using ReflectDefault if let Some(reflect_default) = diff --git a/crates/bevy_ecs/src/entity/clone_entities.rs b/crates/bevy_ecs/src/entity/clone_entities.rs index 3b68f854beb96..472b74b45695b 100644 --- a/crates/bevy_ecs/src/entity/clone_entities.rs +++ b/crates/bevy_ecs/src/entity/clone_entities.rs @@ -1139,11 +1139,11 @@ impl OptOut { #[inline] fn filter_deny(&mut self, id: ComponentId, world: &World) { self.deny.insert(id); - if self.attach_required_by_components { - if let Some(required_by) = world.components().get_required_by(id) { - self.deny.extend(required_by.iter()); - }; - } + if self.attach_required_by_components + && let Some(required_by) = world.components().get_required_by(id) + { + self.deny.extend(required_by.iter()); + }; } } diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index 477cf440deb79..6b7f6c0fc8651 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -229,18 +229,18 @@ impl World { && observers.entity_component_observers.is_empty() { cache.component_observers.remove(component); - if let Some(flag) = Observers::is_archetype_cached(event_key) { - if let Some(by_component) = archetypes.by_component.get(component) { - for archetype in by_component.keys() { - let archetype = &mut archetypes.archetypes[archetype.index()]; - if archetype.contains(*component) { - let no_longer_observed = archetype - .iter_components() - .all(|id| !cache.component_observers.contains_key(&id)); - - if no_longer_observed { - archetype.flags.set(flag, false); - } + if let Some(flag) = Observers::is_archetype_cached(event_key) + && let Some(by_component) = archetypes.by_component.get(component) + { + for archetype in by_component.keys() { + let archetype = &mut archetypes.archetypes[archetype.index()]; + if archetype.contains(*component) { + let no_longer_observed = archetype + .iter_components() + .all(|id| !cache.component_observers.contains_key(&id)); + + if no_longer_observed { + archetype.flags.set(flag, false); } } } diff --git a/crates/bevy_ecs/src/relationship/mod.rs b/crates/bevy_ecs/src/relationship/mod.rs index 2778568dead6e..62849f4977675 100644 --- a/crates/bevy_ecs/src/relationship/mod.rs +++ b/crates/bevy_ecs/src/relationship/mod.rs @@ -188,28 +188,27 @@ pub trait Relationship: Component + Sized { } } let target_entity = world.entity(entity).get::().unwrap().get(); - if let Ok(mut target_entity_mut) = world.get_entity_mut(target_entity) { - if let Some(mut relationship_target) = + if let Ok(mut target_entity_mut) = world.get_entity_mut(target_entity) + && let Some(mut relationship_target) = target_entity_mut.get_mut::() - { - relationship_target.collection_mut_risky().remove(entity); - if relationship_target.len() == 0 { - let command = |mut entity: EntityWorldMut| { - // this "remove" operation must check emptiness because in the event that an identical - // relationship is inserted on top, this despawn would result in the removal of that identical - // relationship ... not what we want! - if entity - .get::() - .is_some_and(RelationshipTarget::is_empty) - { - entity.remove::(); - } - }; - - world - .commands() - .queue_silenced(command.with_entity(target_entity)); - } + { + relationship_target.collection_mut_risky().remove(entity); + if relationship_target.len() == 0 { + let command = |mut entity: EntityWorldMut| { + // this "remove" operation must check emptiness because in the event that an identical + // relationship is inserted on top, this despawn would result in the removal of that identical + // relationship ... not what we want! + if entity + .get::() + .is_some_and(RelationshipTarget::is_empty) + { + entity.remove::(); + } + }; + + world + .commands() + .queue_silenced(command.with_entity(target_entity)); } } } diff --git a/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs index a7b5a5dc1a011..a33c6e556ef2d 100644 --- a/crates/bevy_ecs/src/system/system.rs +++ b/crates/bevy_ecs/src/system/system.rs @@ -442,10 +442,10 @@ where // Note that the `downcast_mut` check is based on the static type, // and can be optimized out after monomorphization. let any: &mut dyn Any = &mut value; - if let Some(err) = any.downcast_mut::() { - if err.skipped { - return Self::Skipped(core::mem::replace(err, SystemParamValidationError::EMPTY)); - } + if let Some(err) = any.downcast_mut::() + && err.skipped + { + return Self::Skipped(core::mem::replace(err, SystemParamValidationError::EMPTY)); } Self::Failed(From::from(value)) } From 45a0eca39d1f1c787adf13b17bf9b17df1803414 Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Thu, 13 Nov 2025 21:33:48 -0500 Subject: [PATCH 6/7] fix: even more clippy lints --- crates/bevy_ecs/src/error/bevy_error.rs | 20 ++++++++------------ crates/bevy_ecs/src/query/fetch.rs | 5 ++--- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/crates/bevy_ecs/src/error/bevy_error.rs b/crates/bevy_ecs/src/error/bevy_error.rs index c290e249b2cfd..a6dea83f829e6 100644 --- a/crates/bevy_ecs/src/error/bevy_error.rs +++ b/crates/bevy_ecs/src/error/bevy_error.rs @@ -191,11 +191,10 @@ mod tests { // On mac backtraces can start with Backtrace::create let mut skip = false; - if let Some(line) = lines.peek() { - if &line[6..] == "std::backtrace::Backtrace::create" { + if let Some(line) = lines.peek() + && &line[6..] == "std::backtrace::Backtrace::create" { skip = true; } - } if skip { lines.next().unwrap(); @@ -212,11 +211,10 @@ mod tests { let line = lines.next().unwrap(); assert_eq!(&line[6..], expected); let mut skip = false; - if let Some(line) = lines.peek() { - if line.starts_with(" at") { + if let Some(line) = lines.peek() + && line.starts_with(" at") { skip = true; } - } if skip { lines.next().unwrap(); @@ -225,20 +223,18 @@ mod tests { // on linux there is a second call_once let mut skip = false; - if let Some(line) = lines.peek() { - if &line[6..] == "core::ops::function::FnOnce::call_once" { + if let Some(line) = lines.peek() + && &line[6..] == "core::ops::function::FnOnce::call_once" { skip = true; } - } if skip { lines.next().unwrap(); } let mut skip = false; - if let Some(line) = lines.peek() { - if line.starts_with(" at") { + if let Some(line) = lines.peek() + && line.starts_with(" at") { skip = true; } - } if skip { lines.next().unwrap(); diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 4e987bbc62feb..03ca458512d9a 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -3214,11 +3214,10 @@ mod tests { fn system(query: Query) { for entity_ref in &query { - if let Some(c) = entity_ref.get_ref::() { - if !c.is_added() { + if let Some(c) = entity_ref.get_ref::() + && !c.is_added() { panic!("Expected C to be added"); } - } } } From e75bb7a3040cb4e9f6a9e9291c47656cfbc300c4 Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Thu, 13 Nov 2025 21:36:44 -0500 Subject: [PATCH 7/7] refactor: rustfmt --- crates/bevy_ecs/src/error/bevy_error.rs | 28 ++++++++++++++----------- crates/bevy_ecs/src/query/fetch.rs | 7 ++++--- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/crates/bevy_ecs/src/error/bevy_error.rs b/crates/bevy_ecs/src/error/bevy_error.rs index a6dea83f829e6..352162ae826a1 100644 --- a/crates/bevy_ecs/src/error/bevy_error.rs +++ b/crates/bevy_ecs/src/error/bevy_error.rs @@ -192,9 +192,10 @@ mod tests { // On mac backtraces can start with Backtrace::create let mut skip = false; if let Some(line) = lines.peek() - && &line[6..] == "std::backtrace::Backtrace::create" { - skip = true; - } + && &line[6..] == "std::backtrace::Backtrace::create" + { + skip = true; + } if skip { lines.next().unwrap(); @@ -212,9 +213,10 @@ mod tests { assert_eq!(&line[6..], expected); let mut skip = false; if let Some(line) = lines.peek() - && line.starts_with(" at") { - skip = true; - } + && line.starts_with(" at") + { + skip = true; + } if skip { lines.next().unwrap(); @@ -224,17 +226,19 @@ mod tests { // on linux there is a second call_once let mut skip = false; if let Some(line) = lines.peek() - && &line[6..] == "core::ops::function::FnOnce::call_once" { - skip = true; - } + && &line[6..] == "core::ops::function::FnOnce::call_once" + { + skip = true; + } if skip { lines.next().unwrap(); } let mut skip = false; if let Some(line) = lines.peek() - && line.starts_with(" at") { - skip = true; - } + && line.starts_with(" at") + { + skip = true; + } if skip { lines.next().unwrap(); diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 03ca458512d9a..71260b188d470 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -3215,9 +3215,10 @@ mod tests { fn system(query: Query) { for entity_ref in &query { if let Some(c) = entity_ref.get_ref::() - && !c.is_added() { - panic!("Expected C to be added"); - } + && !c.is_added() + { + panic!("Expected C to be added"); + } } }