External Publication
Visit Post

Interior mutability and safety of ownership transfer in Rust

Rust Internals [Unofficial] March 5, 2026
Source

increasing:

If i understand correctly this is enough here, as any data behind an indirection won't be copied by ptr::read/write and so won't be duplicated during the reallocation.

Yes, to me that seems to be the case. "Shallow" interior mutability is a problem that can lead to data races, but interior mutability behind an indirection layer should be fine if the duplication of values is in itself not unsound.

But it seems my description was not as clear as I had thought it was, so I'll try to explain it a bit better (hopefully).

So, a simplified version of the buffer type could look like this:

struct Buffer<K, V> {
    len: usize,
    pairs: [(K, V)], // Note: This field makes Buffer a DST!
}

A pseudo-code function for growing a buffer would roughly look like this:

struct HashMap<K, V> {
    buf: AtomicPtr<Buffer<K, V>>,
    rcu: Rcu,
}

impl<K, V> HashMap<K, V>
where
    K: Hash + Eq + Freeze,
    V: Freeze,
{
    unsafe fn grow_buffer(&self) {
        let old: &Buffer<K, V> = &*self.buf.load(Ordering::Acquire);
        let new: *mut Buffer<K, V> = Buffer::alloc_buffer(old.len * 2);

        // semantically "move" all (K, V) from old to new
        for i in 0..old.len {
            let src: *const (K, V) = &old.pairs[i] as _;
            // get  a raw pointer to the (K,V) pair through a raw pointer
            let dst: *mut (K, V) = &raw mut (*new.pairs[i]);
            // (K, V) exists in old and new at the same time!
            // readers can still access `old` and get shared references
            // into it.
            dst.write(src.read());
        }

        // after this swap *new* readers will see the same values that exist
        // in `old` but at a different memory location (only shared references)
        let _ = self.buf.swap(new, Ordering::Release);
        // This will block until no reader that started before the atomic swap is
        // accessing `old` any more, i.e., there can no longer be *any* shared
        // references into `old`.
        self.rcu.synchronize();
        // ... since there are no longer any references, `old` can be freed.
        // this call frees the buffer's memory without dropping any `(K, V)`.
        Buffer::<K, V>::free(old as *mut Buffer<K, V>);
    }
}

I hope this pseudo code is maybe a little easier to follow than my initial description. I doubt this would compile but it's close enough to what the real code would look like.

Of course, the actual code would be significantly more complex with lots of UnsafeCells and MaybeUninits sprinkled throughout.

Discussion in the ATmosphere

Loading comments...