22 个不稳定版本 (7 个重大更改)
0.14.1 | 2024 年 8 月 2 日 |
---|---|
0.14.0 | 2024 年 7 月 4 日 |
0.14.0-rc.4 | 2024 年 6 月 27 日 |
0.13.1 | 2024 年 3 月 18 日 |
0.8.0 | 2022 年 7 月 30 日 |
#2305 在 游戏开发
91,481 每月下载量
在 1,166 个包中 使用 (直接使用 7 个)
35KB
297 行
Bevy 指针
在计算机编程中,指针是存储内存地址的对象。它们是构建更复杂数据结构的基本构建块。
它们也是内存安全问题的根本原因:您可以对无效(null)指针进行解引用,在底层内存被释放后访问指针,甚至忽略类型安全并错误地读取或修改底层内存。
Rust 是一种依赖于其类型来强制执行正确性和内存安全的编程语言。因此,Rust 拥有一系列用于处理指针的类型,以及一系列安全和不可安全转换的图表,这使得它们更安全。
bevy_ptr
是一个包,它试图弥合完全不可安全性 *mut ()
和安全的 &'a T
之间的差距,使用户能够选择对指针保持哪些不变性,旨在逐步构建更安全的抽象。
如何从头开始构建借用
正确且安全地将指针转换为有效借用是 Rust 中所有 unsafe
代码的核心。查看 [(*const T)::as_ref
] 的文档,指针必须满足以下所有条件
- 指针必须正确对齐。
- 指针不能为空,即使是零大小类型。
- 指针必须在有效分配对象的有效范围内(堆栈或堆)。
- 指针必须指向
T
的初始化实例。 - 新分配的生存期应有效针对指针指向的值。
- 代码必须遵守Rust的别名规则。在任何给定时刻,对某个值的借用只能是唯一的可变借用或任意数量的不可变借用,并且从
&T
转换为&mut T
是不被允许的。
请注意,这些规则并非最终版本,并且随着Rust项目确定具体的指针别名规则,这些规则仍在变化,但预期最终的约束集将包含这个列表中的所有规则,而不是子集。
单独满足这个列表已经是非同小可的。幸运的是,Rust核心/标准库提供了一系列指针类型,有助于构建这些安全保证...
标准指针
指针类型 | 有生命周期 | 可变 | 强类型 | 对齐 | 非空 | 禁止别名 | 禁止算术操作 |
---|---|---|---|---|---|---|---|
Box<T> |
所有者 | 是 | 是 | 是 | 是 | 是 | 是 |
&'a mutT |
是 | 是 | 是 | 是 | 是 | 是 | 是 |
&'aT |
是 | 否 | 是 | 是 | 是 | 否 | 是 |
&'a UnsafeCell<T> |
是 | 可能 | 是 | 是 | 是 | 是 | 是 |
NonNull<T> |
否 | 是 | 是 | 否 | 是 | 否 | 否 |
*constT |
否 | 否 | 是 | 否 | 否 | 否 | 否 |
*mutT |
否 | 是 | 是 | 否 | 否 | 否 | 否 |
*const () |
否 | 否 | 否 | 否 | 否 | 否 | 否 |
*mut () |
否 | 是 | 否 | 否 | 否 | 否 | 否 |
&T
、&mut T
以及Box<T>
是Rust开发者最常看到的指针类型。它们是这个列表中唯一完全不需要使用unsafe
即可使用的类型。
&UnsafeCell<T>
是走向不安全的第一个步骤。UnsafeCell
是在语言中从不可变借用获得可变借用的唯一方式,因此它是语言中所有内部可变性的基础原始类型:Cell<T>
、RefCell<T>
、Mutex<T>
、RwLock<T>
等都是建立在UnsafeCell<T>
之上的。为了将&UnsafeCell<T>
安全地转换为&T
或&mut T
,调用者必须保证所有同时访问都遵循Rust的别名规则。
NonNull<T>
与上述类型相比,下降了一个很大的台阶。除了允许别名之外,它是这个列表中第一个放弃借用生命周期和对齐保证的指针类型。它唯一保证的是指针不是空指针,并且它指向类型T
的有效实例。如果你曾经使用过C++,那么NonNull<T>
与C++引用(T&
)非常相似。
*const T
和*mut T
是大多数有C或C++背景的开发者所认为的指针。
*const ()
是这个列表的底部。它们是 Rust 中与 C 的 void*
等效的。请注意,Rust 并没有正式的类型概念来持有任意未类型化的内存地址。指向单元类型(或某些其他零大小类型)只是恰好是惯例。使用它们的唯一合理方式是将它们转换回有类型指针。它们在处理 FFI 以及偶尔需要动态分派但接口过于限制时会出现。一个很好的例子是 RawWaker API,其中单个特质(或一组特质)可能不足以捕获所有使用模式。*mut ()
应仅用于携带目标的可变性,因为没有方法可以突变未知类型。
在 Nightly 版本中可用
指针类型 | 有生命周期 | 可变 | 强类型 | 对齐 | 非空 | 禁止别名 | 禁止算术操作 |
---|---|---|---|---|---|---|---|
独特<T> |
所有者 | 是 | 是 | 是 | 是 | 是 | 是 |
共享<T> |
拥有* | 是 | 是 | 是 | 是 | 否 | 是 |
Unique<T>
目前在 nightly Rust 构建中可在 core::ptr
中使用。它是一种指针类型,表现得像它拥有所指向的值。它可以被视为一个不分配初始化时或释放时不会释放的 Box<T>
,实际上用于实现常见的类型,如 Box<T>
、Vec<T>
等。
Shared<T>
目前在 nightly Rust 构建中可在 core::ptr
中使用。它是支持 Rc<T>
和 Arc<T>
的指针。它的语义允许多个实例共同拥有所指向的数据,因此禁止获取可变借用。
bevy_ptr
目前不支持这些类型,但如果需要,可能会支持这些指针类型的 polyfills。
在 bevy_ptr
中可用
指针类型 | 有生命周期 | 可变 | 强类型 | 对齐 | 非空 | 禁止别名 | 禁止算术操作 |
---|---|---|---|---|---|---|---|
ConstNonNull<T> |
否 | 否 | 是 | 否 | 是 | 否 | 是 |
ThinSlicePtr<'a, T> |
是 | 否 | 是 | 是 | 是 | 是 | 是 |
OwningPtr<'a> |
是 | 是 | 否 | 可能 | 是 | 是 | 否 |
Ptr<'a> |
是 | 否 | 否 | 可能 | 是 | 否 | 否 |
PtrMut<'a> |
是 | 是 | 否 | 可能 | 是 | 是 | 否 |
ConstNonNull<T>
与 NonNull<T>
类似,但禁止将它们安全地转换为允许对所指向的值进行可变访问的类型。它是 *const T
到 NonNull<T>
的 *mut T
。
ThinSlicePtr<'a, T>
是一个没有切片长度的 &'a [T]
。这意味着它在栈上更小,但是这也意味着本地无法进行边界检查,因此访问切片中的元素是 unsafe
。在调试构建中,长度被包含并将进行检查。
OwningPtr<'a>
、Ptr<'a>
和 PtrMut<'a>
类似于 NonNull<()>
,但尝试恢复 Unique<T>
、&T
和 &mut T
的许多安全保证。它们允许在不引入动态分派开销的情况下,以递归的方式将异构类型擦除存储(例如 ECS 表、typemaps)转换为安全的借用。这些类型还支持在类型级别上的可选对齐要求,并在调试构建中验证它。