Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do Arc and Mutex allow me to change the value of an immutable variable?

Tags:

rust

Take this example:

fn main() {
    let dato = std::sync::Arc::new(std::sync::Mutex::new(1u8));

    for _ in 0..3 {
        let value = dato.clone();

        std::thread::spawn(move || {
            let v = value.lock().unwrap();
            *v += 1; // <- Error
        });
    }

    std::thread::sleep(std::time::Duration::from_secs(1u64));

    println!("{:?}", dato);
}

cannot borrow immutable local variable v as mutable

I know that changing to mut works:

std::thread::spawn(move || {
    let mut v = value.lock().unwrap();
    *v += 1;
});

but why does this work:

let value = dato.clone();

std::thread::spawn(move || {
    *value.lock().unwrap() += 1;    
});

playground

like image 260
Angel Angel Avatar asked Apr 21 '16 15:04

Angel Angel


1 Answers

value.lock().unwrap() returns a value of type MutexGuard, which has a DerefMut implementation:

impl<'mutex, T: ?Sized> DerefMut for MutexGuard<'mutex, T> {
    fn deref_mut(&mut self) -> &mut T { ... }
}

DerefMut::deref_mut(x) is equivalent to &mut *x; naturally, DerefMut is also used for assignments under the pointer, like in your case.

Therefore, for *v += 1 to work, v should be a mut variable - otherwise it would be impossible for DerefMut::deref_mut to be invoked at all.

*value.lock().unwrap() += 1 works because now value.lock().unwrap() is a temporary variable without an explicit binding, so Rust is free to assign its mutability automatically.

The fact that Mutex contains an UnsafeCell inside is not related to this particular thing about DerefMut directly; however, it does mean that Mutex provides something called internal mutability, i.e. it allows one to mutate its contents through a shared reference. You can read more on it in the book.

like image 171
Vladimir Matveev Avatar answered Sep 30 '22 17:09

Vladimir Matveev