bevy/crates/bevy_ptr
Zachary Harrold 5241e09671
Upgrade to Rust Edition 2024 (#17967)
# Objective

- Fixes #17960

## Solution

- Followed the [edition upgrade
guide](https://doc.rust-lang.org/edition-guide/editions/transitioning-an-existing-project-to-a-new-edition.html)

## Testing

- CI

---

## Summary of Changes

### Documentation Indentation

When using lists in documentation, proper indentation is now linted for.
This means subsequent lines within the same list item must start at the
same indentation level as the item.

```rust
/* Valid */
/// - Item 1
///   Run-on sentence.
/// - Item 2
struct Foo;

/* Invalid */
/// - Item 1
///     Run-on sentence.
/// - Item 2
struct Foo;
```

### Implicit `!` to `()` Conversion

`!` (the never return type, returned by `panic!`, etc.) no longer
implicitly converts to `()`. This is particularly painful for systems
with `todo!` or `panic!` statements, as they will no longer be functions
returning `()` (or `Result<()>`), making them invalid systems for
functions like `add_systems`. The ideal fix would be to accept functions
returning `!` (or rather, _not_ returning), but this is blocked on the
[stabilisation of the `!` type
itself](https://doc.rust-lang.org/std/primitive.never.html), which is
not done.

The "simple" fix would be to add an explicit `-> ()` to system
signatures (e.g., `|| { todo!() }` becomes `|| -> () { todo!() }`).
However, this is _also_ banned, as there is an existing lint which (IMO,
incorrectly) marks this as an unnecessary annotation.

So, the "fix" (read: workaround) is to put these kinds of `|| -> ! { ...
}` closuers into variables and give the variable an explicit type (e.g.,
`fn()`).

```rust
// Valid
let system: fn() = || todo!("Not implemented yet!");
app.add_systems(..., system);

// Invalid
app.add_systems(..., || todo!("Not implemented yet!"));
```

### Temporary Variable Lifetimes

The order in which temporary variables are dropped has changed. The
simple fix here is _usually_ to just assign temporaries to a named
variable before use.

### `gen` is a keyword

We can no longer use the name `gen` as it is reserved for a future
generator syntax. This involved replacing uses of the name `gen` with
`r#gen` (the raw-identifier syntax).

### Formatting has changed

Use statements have had the order of imports changed, causing a
substantial +/-3,000 diff when applied. For now, I have opted-out of
this change by amending `rustfmt.toml`

```toml
style_edition = "2021"
```

This preserves the original formatting for now, reducing the size of
this PR. It would be a simple followup to update this to 2024 and run
`cargo fmt`.

### New `use<>` Opt-Out Syntax

Lifetimes are now implicitly included in RPIT types. There was a handful
of instances where it needed to be added to satisfy the borrow checker,
but there may be more cases where it _should_ be added to avoid
breakages in user code.

### `MyUnitStruct { .. }` is an invalid pattern

Previously, you could match against unit structs (and unit enum
variants) with a `{ .. }` destructuring. This is no longer valid.

### Pretty much every use of `ref` and `mut` are gone

Pattern binding has changed to the point where these terms are largely
unused now. They still serve a purpose, but it is far more niche now.

### `iter::repeat(...).take(...)` is bad

New lint recommends using the more explicit `iter::repeat_n(..., ...)`
instead.

## Migration Guide

The lifetimes of functions using return-position impl-trait (RPIT) are
likely _more_ conservative than they had been previously. If you
encounter lifetime issues with such a function, please create an issue
to investigate the addition of `+ use<...>`.

## Notes

- Check the individual commits for a clearer breakdown for what
_actually_ changed.

---------

Co-authored-by: François Mockers <francois.mockers@vleue.com>
2025-02-24 03:54:47 +00:00
..
src Upgrade to Rust Edition 2024 (#17967) 2025-02-24 03:54:47 +00:00
Cargo.toml Upgrade to Rust Edition 2024 (#17967) 2025-02-24 03:54:47 +00:00
LICENSE-APACHE Cleanup publish process (#17728) 2025-02-09 17:46:19 +00:00
LICENSE-MIT Cleanup publish process (#17728) 2025-02-09 17:46:19 +00:00
README.md Add README.md to all crates (#13184) 2024-05-02 18:56:00 +00:00

Bevy Pointer

License Crates.io Downloads Docs Discord

Pointers in computer programming are objects that store a memory address. They're a fundamental building block for constructing more complex data structures.

They're also the definitive source of memory safety bugs: you can dereference a invalid (null) pointer, access a pointer after the underlying memory has been freed, and even ignore type safety and misread or mutate the underlying memory improperly.

Rust is a programming language that heavily relies on its types to enforce correctness, and by proxy, memory safety. As a result, Rust has an entire zoo of types for working with pointers, and a graph of safe and unsafe conversions that make working with them safer.

bevy_ptr is a crate that attempts to bridge the gap between the full blown unsafety of *mut () and the safe &'a T, allowing users to choose what invariants to uphold for their pointer, with the intent to enable building progressively safer abstractions.

How to Build a Borrow (From Scratch)

Correctly and safety converting a pointer into a valid borrow is at the core of all unsafe code in Rust. Looking at the documentation for [(*const T)::as_ref], a pointer must satisfy all of the following conditions:

  • The pointer must be properly aligned.
  • The pointer cannot be null, even for zero sized types.
  • The pointer must be within bounds of a valid allocated object (on the stack or the heap).
  • The pointer must point to an initialized instance of T.
  • The newly assigned lifetime should be valid for the value that the pointer is targeting.
  • The code must enforce Rust's aliasing rules. Only one mutable borrow or arbitrarily many read-only borrows may exist to a value at any given moment in time, and converting from &T to &mut T is never allowed.

Note these rules aren't final and are still in flux as the Rust Project hashes out what exactly are the pointer aliasing rules, but the expectation is that the final set of constraints are going to be a superset of this list, not a subset.

This list already is non-trivial to satisfy in isolation. Thankfully, the Rust core/standard library provides a progressive list of pointer types that help build these safety guarantees...

Standard Pointers

Pointer Type Lifetime'ed Mutable Strongly Typed Aligned Not Null Forbids Aliasing Forbids Arithmetic
Box<T> Owned Yes Yes Yes Yes Yes Yes
&'a mut T Yes Yes Yes Yes Yes Yes Yes
&'a T Yes No Yes Yes Yes No Yes
&'a UnsafeCell<T> Yes Maybe Yes Yes Yes Yes Yes
NonNull<T> No Yes Yes No Yes No No
*const T No No Yes No No No No
*mut T No Yes Yes No No No No
*const () No No No No No No No
*mut () No Yes No No No No No

&T, &mut T, and Box<T> are by far the most common pointer types that Rust developers will see. They're the only ones in this list that are entirely usable without the use of unsafe.

&UnsafeCell<T> is the first step away from safety. UnsafeCell is the only way to get a mutable borrow from an immutable one in the language, so it's the base primitive for all interior mutability in the language: Cell<T>, RefCell<T>, Mutex<T>, RwLock<T>, etc. are all built on top of UnsafeCell<T>. To safety convert &UnsafeCell<T> into a &T or &mut T, the caller must guarantee that all simultaneous access follow Rust's aliasing rules.

NonNull<T> takes quite a step down from the aforementioned types. In addition to allowing aliasing, it's the first pointer type on this list to drop both lifetimes and the alignment guarantees of borrows. Its only guarantees are that the pointer is not null and that it points to a valid instance of type T. If you've ever worked with C++, NonNull<T> is very close to a C++ reference (T&).

*const T and *mut T are what most developers with a background in C or C++ would consider pointers.

*const () is the bottom of this list. They're the Rust equivalent to C's void*. Note that Rust doesn't formally have a concept of type that holds an arbitrary untyped memory address. Pointing at the unit type (or some other zero-sized type) just happens to be the convention. The only way to reasonably use them is to cast back to a typed pointer. They show up occasionally when dealing with FFI and the rare occasion where dynamic dispatch is required, but a trait is too constraining of an interface to work with. A great example of this are the RawWaker APIs, where a singular trait (or set of traits) may be insufficient to capture all usage patterns. *mut () should only be used to carry the mutability of the target, and as there is no way to mutate an unknown type.

Available in Nightly

Pointer Type Lifetime'ed Mutable Strongly Typed Aligned Not Null Forbids Aliasing Forbids Arithmetic
Unique<T> Owned Yes Yes Yes Yes Yes Yes
Shared<T> Owned* Yes Yes Yes Yes No Yes

Unique<T> is currently available in core::ptr on nightly Rust builds. It's a pointer type that acts like it owns the value it points to. It can be thought of as a Box<T> that does not allocate on initialization or deallocated when it's dropped, and is in fact used to implement common types like Box<T>, Vec<T>, etc.

Shared<T> is currently available in core::ptr on nightly Rust builds. It's the pointer that backs both Rc<T> and Arc<T>. Its semantics allow for multiple instances to collectively own the data it points to, and as a result, forbids getting a mutable borrow.

bevy_ptr does not support these types right now, but may support polyfills for these pointer types if the need arises.

Available in bevy_ptr

Pointer Type Lifetime'ed Mutable Strongly Typed Aligned Not Null Forbids Aliasing Forbids Arithmetic
ConstNonNull<T> No No Yes No Yes No Yes
ThinSlicePtr<'a, T> Yes No Yes Yes Yes Yes Yes
OwningPtr<'a> Yes Yes No Maybe Yes Yes No
Ptr<'a> Yes No No Maybe Yes No No
PtrMut<'a> Yes Yes No Maybe Yes Yes No

ConstNonNull<T> is like NonNull<T> but disallows safe conversions into types that allow mutable access to the value it points to. It's the *const T to NonNull<T>'s *mut T.

ThinSlicePtr<'a, T> is a &'a [T] without the slice length. This means it's smaller on the stack, but it means bounds checking is impossible locally, so accessing elements in the slice is unsafe. In debug builds, the length is included and will be checked.

OwningPtr<'a>, Ptr<'a>, and PtrMut<'a> act like NonNull<()>, but attempts to restore much of the safety guarantees of Unique<T>, &T, and &mut T. They allow working with heterogenous type erased storage (i.e. ECS tables, typemaps) without the overhead of dynamic dispatch in a manner that progressively translates back to safe borrows. These types also support optional alignment requirements at a type level, and will verify it on dereference in debug builds.