Struct std::cell::UnsafeCell 1.0.0[−][src]
#[repr(transparent)]#[repr(no_niche)]pub struct UnsafeCell<T> where
T: ?Sized, { /* fields omitted */ }
Expand description
Rust 中内部可变性的核心原语。
如果您使用的是 &T
,则通常在 Rust 中,编译器基于 &T
指向不可变数据的知识来执行优化。例如通过别名或通过将 &T
转换为 &mut T
来可变的该数据,被认为是未定义的行为。
UnsafeCell<T>
选择不使用 &T
的不可替代保证: 共享的 &UnsafeCell<T>
可能指向正在可变的的数据。这称为内部可变性。
所有其他允许内部可变性的类型 (例如 Cell<T>
和 RefCell<T>
) 在内部都使用 UnsafeCell
封装其数据。
请注意,仅 UnsafeCell
会影响共享引证的不可变性保证。可变引用的唯一性保证不受影响。没有合法的方法来获得 &mut
的别名,即使使用 UnsafeCell<T>
也没有。
UnsafeCell
API 本身在技术上非常简单: .get()
为其内容提供了裸指针 *mut T
。正确使用该裸指针取决于您。
精确的 Rust 别名规则有些变化,但是要点并不存在争议:
-
如果使用生命周期
'a
(&T
或&mut T
引用) 创建可以通过安全代码访问的安全引用 (例如,由于返回了它),则不得以任何与该引用的引用相矛盾的方式访问数据'a
的其余部分。 例如,这意味着如果您从UnsafeCell<T>
中取出*mut T
并将其转换为&T
,则T
中的数据必须保持不可变 (当然,对T
中找到的任何UnsafeCell
数据取模),直到引用的生命周期到期为止。 同样,如果您创建的&mut T
引用已发布为安全代码,则在引用终止之前,您不得访问UnsafeCell
中的数据。 -
在任何时候,您都必须避免数据竞争。如果多个线程可以访问同一个
UnsafeCell
,那么任何写操作都必须在与所有其他访问 (或使用原子) 相关之前发生正确的事件。
为了帮助进行正确的设计,以下情况明确声明为单线程代码合法:
-
&T
引用可以释放为安全代码,并且可以与其他&T
引用共存,但不能与&mut T
共存 -
&mut T
引用可以发布为安全代码,前提是其他&mut T
和&T
都不共存。&mut T
必须始终是唯一的。
请注意,虽然可以更改 &UnsafeCell<T>
的内容 (即使其他 &UnsafeCell<T>
引用了该 cell 的别名) 也可以 (只要以其他方式实现上述不变式即可),但是具有多个 &mut UnsafeCell<T>
别名仍然是未定义的行为。
也就是说,UnsafeCell
是一个包装器,旨在通过 &UnsafeCell<_>
与 shared accesses (i.e. 进行特殊交互 (引用) ; 通过 &mut UnsafeCell<_>
处理 exclusive accesses (e.g. 时没有任何魔术) : 在该 &mut
借用期间, cell 和包装值都不能被别名。
.get_mut()
访问器展示了这一点,该访问器是产生 &mut T
的 safe getter。
Examples
这是一个示例,展示了如何对 UnsafeCell<_>
的内容进行合理的可变的,尽管该单元存在多个引用别名:
use std::cell::UnsafeCell; let x: UnsafeCell<i32> = 42.into(); // 对同一个 `x` 获取多个 / 并发 / 共享引用。 let (p1, p2): (&UnsafeCell<i32>, &UnsafeCell<i32>) = (&x, &x); unsafe { // SAFETY: 在此作用域内,对 x 的内容没有其他引用,因此我们的内容实际上是唯一的。 let p1_exclusive: &mut i32 = &mut *p1.get(); // -- 借用 --+ *p1_exclusive += 27; // | } // <---------- 不能超出这一点 --- ----------------+ unsafe { // SAFETY: 在此作用域内,没有人期望对 x 的内容具有独占访问权,因此我们可以同时进行多个共享访问。 let p2_shared: &i32 = &*p2.get(); assert_eq!(*p2_shared, 42 + 27); let p1_shared: &i32 = &*p1.get(); assert_eq!(*p1_shared, *p2_shared); }Run
以下示例展示了对 UnsafeCell<T>
的独占访问意味着对其 T
的独占访问的事实:
#![forbid(unsafe_code)] // 具有独占访问权, // `UnsafeCell` 是透明的无操作包装器,因此这里不需要 `unsafe`。 use std::cell::UnsafeCell; let mut x: UnsafeCell<i32> = 42.into(); // 获得对 `x` 进行编译时检查的唯一引用。 let p_unique: &mut UnsafeCell<i32> = &mut x; // 使用独家引用,我们可以免费更改内容。 *p_unique.get_mut() = 0; // 或者,等效地: x = UnsafeCell::new(0); // 当我们拥有该值时,我们可以免费提取内容。 let contents: i32 = x.into_inner(); assert_eq!(contents, 0);Run
Implementations
获取指向包装值的可变指针。
与 get
的不同之处在于此函数接受裸指针,这对于避免创建临时引用很有用。
结果可以转换为任何类型的指针。
强制转换为 &mut T
时,访问是唯一的 (无活动的引用,可变性或非活动性),并确保转换为 &T
时没有发生任何可变的或可变别名。
Examples
UnsafeCell
的逐步初始化需要 raw_get
,因为调用 get
需要对未初始化的数据创建引用:
#![feature(unsafe_cell_raw_get)] use std::cell::UnsafeCell; use std::mem::MaybeUninit; let m = MaybeUninit::<UnsafeCell<i32>>::uninit(); unsafe { UnsafeCell::raw_get(m.as_ptr()).write(5); } let uc = unsafe { m.assume_init() }; assert_eq!(uc.into_inner(), 5);Run
Trait Implementations
创建一个 UnsafeCell
,其 T 值为 Default
。
执行转换。