[...] what is happening on let data = data.clone()
?
Arc
stands for Atomically Reference Counted. An Arc
manages one object (of type T
) and serves as a proxy to allow for shared ownership, meaning: one object is owned by multiple names. Wow, that sounds abstract, let's break it down!
Shared Ownership
Let's say you have an object of type Turtle
which you bought for your family. Now the problem arises that you can't assign a clear owner of the turtle: every family-member kind of owns that pet! This means (and sorry for being morbid here) that if one member of the family dies, the turtle won't die with that family-member. The turtle will only die if all members of the family are gone as well. Everyone owns and the last one cleans up.
So how would you express that kind of shared ownership in Rust? You will quickly notice that it's impossible to do with only standard methods: you'd always have to choose one owner and everyone else would only have a reference to the turtle. Not good!
So along come Rc
and Arc
(which, for the sake of this story, serve the exact same purpose). These allow for shared ownership by tinkering a bit with unsafe-Rust. Let's look at the memory after executing the following code (note: the memory layout is for learning and might not represent the exact same memory layout from the real world):
let annas = Rc::new(Turtle { legs: 4 });
Memory:
Stack Heap
----- ----
annas:
+--------+ +------------+
| ptr: o-|-------------->| count: 1 |
+--------+ | data: |
+------------+
We see that the turtle lives on the heap... next to a counter which is set to 1. This counter knows how many owners the object data
currently has. And 1 is correct: annas
is the only one owning the turtle right now. Let's clone()
the Rc
to get more owners:
let peters = annas.clone();
let bobs = annas.clone();
Now the memory looks like this:
Stack Heap
----- ----
annas:
+--------+ +------------+
| ptr: o-|-------------->| count: 3 |
+--------+ ^ | data: |
| +------------+
peters: |
+--------+ |
| ptr: o-|----+
+--------+ ^
|
bobs: |
+--------+ |
| ptr: o-|----+
+--------+
As you can see, the turtle still exists only once. But the reference count was increased and is now 3, which makes sense, because the turtle has three owners now. All those three owners reference this memory block on the heap. That's what the Rust book calls owned handle: each owner of such a handle also kind of owns the underlying object.
(also see "Why is std::rc::Rc<>
not Copy?")
Atomicity and Mutability
What's the difference between Arc<T>
and Rc<T>
you ask? The Arc
increments and decrements its counter in an atomic fashion. That means that multiple threads can increment and decrement the counter simultaneously without a problem. That's why you can send Arc
s across thread-boundaries, but not Rc
s.
Now you notice that you can't mutate the data through an Arc<T>
! What if your loses a leg? Arc
is not designed to allow mutable access from multiple owners at (possibly) the same time. That's why you often see types like Arc<Mutex<T>>
. The Mutex<T>
is a type that offers interior mutability, which means that you can get a &mut T
from a &Mutex<T>
! This would normally conflict with the Rust core principles, but it's perfectly safe because the mutex also manages access: you have to request access to the object. If another thread/source currently has access to the object, you have to wait. Therefore, at one given moment in time, there is only one thread able to access T
.
Conclusion
[...] is each thread modifying the original data instead of a copy?
As you can hopefully understand from the explanation above: yes, each thread is modifying the original data. A clone()
on an Arc<T>
won't clone the T
, but merely create another owned handle; which in turn is just a pointer that behaves as if it owns the underlying object.