如何安全地存储泛型值的不可变、别名副本



摘要

出于优化目的,我希望创建一个数据结构,将单个值存储在多个不同的位置。数据结构只允许通过不可变引用或从数据结构中完全删除这些值来使用这些值。

如何确保这对于泛型类型是安全的?

简化示例

为了给出一个琐碎(但有些不切实际(的上下文示例,考虑一个缓存最近使用的值的切片:

struct Pair<'a> {
    values: &'a [T],
    last_accessed: &'a T,
}

然而,访问最后一个访问的元素仍然会导致指针取消引用,因此代码需要一个按值缓存:

struct Pair<'a> {
    values: &'a [T],
    last_accessed: NoDrop<T>,
}

在大多数情况下,这似乎是安全的。例如,如果Tu32,则缓存只是数据的简单副本。

即使TVec<U>,这似乎也是安全的,因为通过&last_accessed的任何访问都不能改变向量的任何直接成员。堆分配在传递上是不可变的,并且不重复,因此不存在明显的混叠问题。

为什么这么难

这对于所有值都不安全。包含Cell的值可能会调用内部可变性,并最终违反内部约束,当通过未传播该更改的值进行访问时,这些约束会导致不安全的行为。

问题是,我可以对通用T施加什么约束来确保它的安全?据我所知,我只需要它除了通过一个指针之外不包含UnsafeCell

T: Copy

T: Copy看起来是一个不错的解决方案,但有两个主要缺点:

  • "[UnsafeCell]没有实现Copy的绝对根本原因"——Alex Crichton。Copy的要求似乎是巧合而非保证。

  • Copy禁令太多;Vec不是Copy,但不需要被禁止。

T: Sync怎么样

Sync接近正确的想法,因为

Sync的类型是那些以非线程安全的方式具有"内部可变性"的类型,例如std::cell中的CellRefCell

然而,有几种类型,如原子,具有线程安全的内部可变性。

您可以使用自动特征UnsafeCell强制执行约束。这些是默认定义的,但可以使用特定类型的特殊语法选择退出——您只想选择退出UnsafeCell

这些特性以前被称为选择加入内置特性(OIBIT(,但由于它们既不是选择加入特性,也不一定是内置特性,而且实际上是可以在普通用户代码中定义的选择退出特性,因此被重命名。

首先,您启用它,创建特性并在默认情况下实现它。这使用了一些神奇的语法。

#![feature(optin_builtin_traits)]
pub unsafe trait CopyRef {}
unsafe impl CopyRef for .. {}

然后您选择退出UnsafeCell

// Opt out of interior mutability
impl<T: ?Sized> !CopyRef for UnsafeCell<T> {}

然后,您需要重新启用指针后面的UnsafeCellPhantomData

use std::marker::PhantomData;
use std::cell::UnsafeCell;
// Opt in for indirect interior mutability
unsafe impl<'a, T: ?Sized> CopyRef for *const T {}
unsafe impl<'a, T: ?Sized> CopyRef for *mut T {}
unsafe impl<'a, T: ?Sized> CopyRef for &'a T {}
unsafe impl<'a, T: ?Sized> CopyRef for &'a mut T {}
// Box is special and needs its own opt-in
unsafe impl<T: ?Sized> CopyRef for Box<T> {}
// And fake interior mutability
unsafe impl<T: ?Sized> CopyRef for PhantomData<T> {}

瞧。您自己的用户定义的退出选择内置自动Trait。

最新更新