如何安全地存储通用值的不可变别名副本?

How to safely store immutable, aliasing copies of a generic value?

总结

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

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

简化示例

为了给上下文举一个简单的(但有点不切实际)的例子,考虑一个缓存最近使用的值的切片:

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 看起来是一个不错的解决方案,但有两个主要缺点:

T: Sync

Sync 接近正确的想法,因为

Types that are not Sync are those that have "interior mutability" in a non-thread-safe way, such as Cell and RefCell in std::cell.

但是,一些类型,如原子,具有内部可变性,线程安全的。

您可以使用 auto traitUnsafeCell 强制执行约束。这些是默认定义的,但可以使用特定类型的特殊语法选择退出 - 您只想为 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> {}

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