Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is `mem::swap`ing a `Mutex<T>` really safe?

Tags:

rust

A simple example:

use std::mem;
use std::sync::{Mutex};

fn main() {
    let mut orig = Mutex::new(vec![1, 2, 3]);
    let mut other = Mutex::new(vec![]);
    mem::swap(&mut orig, &mut other);
    println!("{:?}", other);
}

This program is, according to Rust, perfectly safe. Yet the implementation of swap (or replace) does not attempt to lock anything. From the source:

#[inline]
#[stable(feature = "rust1", since = "1.0.0")]
pub fn swap<T>(x: &mut T, y: &mut T) {
    unsafe {
        // Give ourselves some scratch space to work with
        let mut t: T = uninitialized();

        // Perform the swap, `&mut` pointers never alias
        ptr::copy_nonoverlapping(&*x, &mut t, 1);
        ptr::copy_nonoverlapping(&*y, x, 1);
        ptr::copy_nonoverlapping(&t, y, 1);

        // y and t now point to the same thing, but we need to completely
        // forget `t` because we do not want to run the destructor for `T`
        // on its value, which is still owned somewhere outside this function.
        forget(t);
    }
}

Using unsynchronized access on Mutex or Atomic variables seems like a recipe for trouble, so is this safe? And if it is, why?

like image 581
Matthieu M. Avatar asked Feb 26 '16 12:02

Matthieu M.


2 Answers

Mutex is Sync, which means it's allowed for multiple threads to access the same object concurrently. Types that make the sharing of a single object across threads possible in the first place (Arc, but also global variables) need to make sure that the objects they share are Sync to ensure integrity.

However, that doesn't avoid Rust's other aliasing rules. Specifically, only one mut borrow of a variable can exist at any given time, and its existence must prevent any other access to the variable. The things that make sharing possible must ensure this, as well. Arc does this by simply never handing out mut references to its pointee; you can only get non-mut references. This is why Mutex::lock takes &self: it must be possible to call it on a non-mut reference. Similarly, Atomic*::store and all other manipulation methods take &self. static mut variables, on the other hand, simply can only be accessed within unsafe code, where the programmer is responsible for upholding guarantees.

mem::swap needs mut references. When the compiler allows you to pass two Mutex objects to swap, it means that you never shared the mutexes with other threads to begin with - you neither put them into Arc, nor are they static (not mut) or static mut (unless you're in unsafe code).

As such, the swap is safe, because you're doing it within one thread.

like image 162
Sebastian Redl Avatar answered Nov 15 '22 02:11

Sebastian Redl


The answer is simple: Mutability XOR Aliasing.

Why is mem::swap safe despite not locking the mutex or executing an atomic operation?

Because &mut T or T guarantees the absence of accessible aliases at compile-time.


The fact that a Mutex or AtomicXXX achieve safe mutation while shared across threads by either several constraining the types and operations (AtomicXXX) or ensuring the "Mutability XOR Aliasing" at run-time (Mutex) is an exception to the compile-time guarantee, but it does not supersede it.

A type can be safely mutated:

  • if, at compile-time, it is not aliased (&mut T or T)
  • if, at run-time, a special property of the type ensures that this is safe

types that have a special property do so on top of following the regular compile-time rules on which the safety of mem::swap and mem::replace hinges, and therefore in situations where the absence of aliasing can be proven at compile-time, they can be mutated without any specific synchronization.

like image 23
Matthieu M. Avatar answered Nov 15 '22 01:11

Matthieu M.