Why can we create code like that:
let x = Box::new(42);
let mut y = x;
*y = 100;
Box is implementing Sync. Isn't its content immutable?
Why can we create code like that:
let x = Box::new(42);
let mut y = x;
*y = 100;
Box is implementing Sync. Isn't its content immutable?
No, as you've said
let mut y = x;
This moves the box from an immutable to an mutable binding. As such, you can mutate what's in the box.
(Sync
has nothing to do with any of this)
Then there is something that I don't understand. If Box is implementing Sync, how can its value be changed? This can create a race condition. I don't get it. Please explain.
I did some testing. Now I understand. If I try to access a variable of type Box from another thread, the thread will own the value and it won't be accessible from the previous reference.
Sync
lets values be shared behind immutable references. See this example with Arc
: Rust Playground. Doing an explicit deref of the Arc only gets you an immutable pointer to its interior.
So if you put a Box
in an Arc
you can't get a mutable pointer to the Box
- it is 'frozen'. This is what allows immutable data to be shared trivially.
The magic then that allows Sync
things to be mutated is 'interior mutability'. Interior mutability provides a way to mutate things behind 'immutable' (i.e. shared) references.
In a concurrent setting, e.g. Mutex
can be used to provide exclusive mutable access to a value. So to mutate a shared box you put a Box
in a Mutex
in an Arc
.
If you want to understand what Sync really means, check out How Rust Achieves Thread Safety - In Pursuit of Laziness
With Rc and Arc, deferencing is automatic. So we can write:
use std::sync::Arc;
use std::ops::Deref;
fn main() {
let foo = Arc::new(0);
let bar: &i8 = &foo.deref();
println!("{}", foo);
}
And it will print 0
.