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?
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.
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:
&mut T
or T
)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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With