Struct std::cell::UnsafeCell

1.0.0 · source ·
#[repr(transparent)]
pub struct UnsafeCell<T>where T: ?Sized,{ /* private fields */ }
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&mut T,在引用过期之前,您也不得释放数据。作为一个特殊的例外,给定一个 &T,它在 UnsafeCell<_> 内的任何部分都可能在引用的生命周期期间被释放,在最后一次使用引用之后 (解引用或重新借用)。 因为您不能释放引用指向的部分,这意味着只有当它的每一部分 (包括填充) 都在 UnsafeCell 中时,&T 指向的内存才能被释放。

    但是,无论何时构造或解引用 &UnsafeCell<T>,它仍必须指向活动内存,并且如果编译器可以证明该内存尚未被释放,则允许编译器插入虚假读取。

  • 在任何时候,您都必须避免数据竞争。如果多个线程可以访问同一个 UnsafeCell,那么任何写操作都必须在与所有其他访问 (或使用原子) 相关之前发生正确的事件。

为了帮助进行正确的设计,以下情况明确声明为单线程代码合法:

  1. &T 引用可以释放为安全代码,并且可以与其他 &T 引用共存,但不能与 &mut T 共存

  2. &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 Tsafe getter。

内存布局

UnsafeCell<T> 与其内部类型 T 具有相同的内存表示。此保证的结果是可以在 TUnsafeCell<T> 之间进行转换。 将 Outer<T> 类型内的嵌套 T 转换为 Outer<UnsafeCell<T>> 类型时必须特别小心: 当 Outer<T> 类型启用 niche 优化时,这不是正确的。例如,类型 Option<NonNull<u8>> 通常是 8 字节大 64 位平台,但 Option<UnsafeCell<NonNull<u8>>> 类型占用 16 字节空间。 因此,这不是有效的转换,尽管 NonNull<u8>UnsafeCell<NonNull<u8>>> 具有相同的内存布局。这是因为 UnsafeCell 禁用了 niche 优化,以避免其内部错误性属性从 T 传播到 Outer 类型,因此在这些情况下这可能导致类型大小的失真。

请注意,获取指向共享 UnsafeCell<T> 内容的 *mut T 指针的唯一有效方法是通过 .get().raw_get()&mut T 引用可以通过解引用 this 指针或通过在独占 UnsafeCell<T> 上调用 .get_mut() 来获得。 即使 TUnsafeCell<T> 具有相同的内存布局,以下是不允许的和未定义的行为:

unsafe fn not_allowed<T>(ptr: &UnsafeCell<T>) -> &mut T {
  let t = ptr as *const UnsafeCell<T> as *mut T;
  // 这是未定义的行为,因为 `*mut T` 指针不是通过 `.get()` 或 `.raw_get()` 获得的:
  unsafe { &mut *t }
}
Run

相反,请执行以下操作:

// 安全: 调用者必须确保没有引用指向 `UnsafeCell` 的*内容*。
unsafe fn get_mut<T>(ptr: &UnsafeCell<T>) -> &mut T {
  unsafe { &mut *ptr.get() }
}
Run

允许在另一个方向从 &mut T 转换为 &UnsafeCell<T>:

fn get_shared<T>(ptr: &mut T) -> &UnsafeCell<T> {
  let t = ptr as *mut T as *const UnsafeCell<T>;
  // SAFETY: `T` 和 `UnsafeCell<T>` 具有相同的内存布局
  unsafe { &*t }
}
Run

Examples

这是一个示例,展示了如何对 UnsafeCell<_> 的内容进行合理的可变的,尽管该 cell 存在多个引用别名:

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§

source§

impl<T> UnsafeCell<T>

const: 1.32.0 · source

pub const fn new(value: T) -> UnsafeCell<T>

创建 UnsafeCell 的新实例,该实例将包装指定的值。

所有通过 &UnsafeCell<T> 访问内部值都需要 unsafe 代码。

Examples
use std::cell::UnsafeCell;

let uc = UnsafeCell::new(5);
Run
const: unstable · source

pub fn into_inner(self) -> T

解开值,消耗 cell。

Examples
use std::cell::UnsafeCell;

let uc = UnsafeCell::new(5);

let five = uc.into_inner();
Run
source§

impl<T> UnsafeCell<T>where T: ?Sized,

source

pub const fn from_mut(value: &mut T) -> &mut UnsafeCell<T>

🔬This is a nightly-only experimental API. (unsafe_cell_from_mut #111645)

&mut T 转换为 &mut UnsafeCell<T>

Examples
use std::cell::UnsafeCell;

let mut val = 42;
let uc = UnsafeCell::from_mut(&mut val);

*uc.get_mut() -= 1;
assert_eq!(*uc.get_mut(), 41);
Run
const: 1.32.0 · source

pub const fn get(&self) -> *mut T

获取指向包装值的可变指针。

可以将其强制转换为任何类型的指针。 强制转换为 &mut T 时,访问是唯一的 (无活跃的引用,可变或不活动),并确保转换为 &T 时没有发生任何可变的或可变别名。

Examples
use std::cell::UnsafeCell;

let uc = UnsafeCell::new(5);

let five = uc.get();
Run
1.50.0 (const: unstable) · source

pub fn get_mut(&mut self) -> &mut T

返回对底层数据的可变引用。

这个调用借用 UnsafeCell (在编译时) 是可变的,这保证了我们拥有唯一的引用。

Examples
use std::cell::UnsafeCell;

let mut c = UnsafeCell::new(5);
*c.get_mut() += 1;

assert_eq!(*c.get_mut(), 6);
Run
1.56.0 (const: 1.56.0) · source

pub const fn raw_get(this: *const UnsafeCell<T>) -> *mut T

获取指向包装值的可变指针。 与 get 的不同之处在于该函数接受一个裸指针,这有助于避免创建临时引用。

结果可以转换为任何类型的指针。 强制转换为 &mut T 时,访问是唯一的 (无活跃的引用,可变性或非活动性),并确保转换为 &T 时没有发生任何可变的或可变别名。

Examples

UnsafeCell 的逐步初始化需要 raw_get,因为调用 get 需要对未初始化的数据创建引用:

use std::cell::UnsafeCell;
use std::mem::MaybeUninit;

let m = MaybeUninit::<UnsafeCell<i32>>::uninit();
unsafe { UnsafeCell::raw_get(m.as_ptr()).write(5); }
// avoid below which 引用未初始化的数据 unsafe { UnsafeCell::get(&*m.as_ptr()).write(5); }
let uc = unsafe { m.assume_init() };

assert_eq!(uc.into_inner(), 5);
Run

Trait Implementations§

1.9.0 · source§

impl<T> Debug for UnsafeCell<T>where T: ?Sized,

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

使用给定的格式化程序格式化该值。 Read more
1.10.0 · source§

impl<T> Default for UnsafeCell<T>where T: Default,

source§

fn default() -> UnsafeCell<T>

创建一个 UnsafeCell,其 T 值为 Default

1.12.0 · source§

impl<T> From<T> for UnsafeCell<T>

source§

fn from(t: T) -> UnsafeCell<T>

创建一个包含给定值的新 UnsafeCell<T>

source§

impl<T, U> CoerceUnsized<UnsafeCell<U>> for UnsafeCell<T>where T: CoerceUnsized<U>,

source§

impl<T, U> DispatchFromDyn<UnsafeCell<U>> for UnsafeCell<T>where T: DispatchFromDyn<U>,

1.9.0 · source§

impl<T> !RefUnwindSafe for UnsafeCell<T>where T: ?Sized,

source§

impl<T> !Sync for UnsafeCell<T>where T: ?Sized,

Auto Trait Implementations§

§

impl<T: ?Sized> Send for UnsafeCell<T>where T: Send,

§

impl<T: ?Sized> Unpin for UnsafeCell<T>where T: Unpin,

§

impl<T: ?Sized> UnwindSafe for UnsafeCell<T>where T: UnwindSafe,

Blanket Implementations§

source§

impl<T> Any for Twhere T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

获取 selfTypeIdRead more
source§

impl<T> Borrow<T> for Twhere T: ?Sized,

source§

fn borrow(&self) -> &T

从拥有的值中一成不变地借用。 Read more
source§

impl<T> BorrowMut<T> for Twhere T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

从拥有的值中借用。 Read more
source§

impl<T> From<!> for T

source§

fn from(t: !) -> T

从输入类型转换为此类型。
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

返回未更改的参数。

source§

impl<T, U> Into<U> for Twhere U: From<T>,

source§

fn into(self) -> U

调用 U::from(self)

也就是说,这种转换是 From<T> for U 实现选择执行的任何操作。

source§

impl<T, U> TryFrom<U> for Twhere U: Into<T>,

§

type Error = Infallible

发生转换错误时返回的类型。
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

执行转换。
source§

impl<T, U> TryInto<U> for Twhere U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

发生转换错误时返回的类型。
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

执行转换。