How to safely store immutable, pseudonizing copies of the total value?

Summary

For optimization purposes, I want to create a data structure that stores individual values ​​in several different places. The data structure will allow you to use these values ​​only through an immutable link or completely remove them from the data structure.

How can I guarantee that it is safe for the universal type?

Simplified example

To give a trivial (but somewhat unrealistic) example for the context, consider a slice that caches the last used value:

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

Access to the last available item, however, still leads to dereferencing the pointer, so the code would like to use a cache bike:

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

In most cases, this seems safe. For example, if T is u32 , the cache is just a simple copy of the data.

Even if T is Vec<U> , it seems safe, since any access through &last_accessed cannot change any of the direct members of the vector. The heap distribution is transitively immutable and not duplicated, so there is no obvious problem with the alias.

Why is it difficult?

This is unsafe for all values. A value containing Cell can cause internal volatility and ultimately violate internal constraints that cause unsafe access behavior through a value that the change did not propagate.

The question is, what restrictions can be put on the general T , which will make it safe? As far as I know, all I need is that it does not contain UnsafeCell , except through a pointer.

How about T: Copy ?

T: Copy looks like a decent solution, but there are two main drawbacks:

  • "There is no absolutely fundamental reason why [ UnsafeCell ] does not implement Copy " - Alex Crichton . The Copy requirement seems random, not guaranteed.

  • Copy too many bans; Vec not Copy , but it does not need to be disabled.

How about T: Sync

Sync is close to the right idea, as

Types that are not Sync are those that have an “intrinsic volatility” in an unsafe manner, such as Cell and RefCell in std::cell .

However, several types, such as atoms, have intrinsic variability, which is thread safe.

+6
source share
1 answer

You can force a restriction to UnsafeCell using an automatic UnsafeCell . They are defined by default, but can be excluded from the special syntax for certain types - you only want to refuse UnsafeCell .

They were formerly called Opt-In Embedded Features (OIBITs), but were renamed because they are neither failures nor necessarily embedded, and in fact are indications of failure that can be defined in regular user code.

First you enable it, create a feature and make it implemented by default. This uses some kind of magic syntax.

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

Then you give up UnsafeCell .

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

Then you will want to re-enable UnsafeCell for pointers and PhantomData .

 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> {} 

Voila. Your own custom Opt-In Auto-Trait auto-ban.

+6
source

All Articles