Why does `Arc <T>` require that T be both `Send` and` Sync` in order to be `Send` /` Sync` itself?
Arc<T>
documentation says:
impl<T> Sync for Arc<T> where T: Send + Sync + ?Sized impl<T> Send for Arc<T> where T: Send + Sync + ?Sized
An Arc
allows multiple threads to access the underlying T
at the same time via an &T
immutable link. This is safe until T
can be changed in an unsynchronized manner via &T
This is true for all types with "inherited variability" (almost all types) and false for those with unsynchronized "internal variability" (for example, RefCell
, ...).
As far as I understand, Send
binding is not required here. For example, I think my shared artificial type, which implements Sync
but not Send
in Arc
, is safe.
Finally, &T
itself also does not have this binding! In the documentation for Send
and Sync
we find:
impl<'a, T> Send for &'a T where T: Sync + ?Sized impl<'a, T> Sync for &'a T where T: Sync + ?Sized
And since Arc<T>
allows the same access to T
as &T
, I don’t understand why Arc<T>
has an additional Send
binding. Why is this?
I believe this is because Arc
owns the value that it contains, and therefore is responsible for removing it.
Consider the following sequence:
- In stream 1, a value of type
T
. This is notSend
, which means that transferring this value to another thread is unsafe. - This value is moved to the
Arc
descriptor. - The descriptor clone is sent to stream 2.
- The handle stored in stream 1 is discarded.
- The handle stored in stream 2 is discarded. Since this is the last handle, it assumes full ownership of the stored value and omits it.
And also we moved a value of type T
from one thread to another, violating memory security.
&T
does not require Send
, because deleting &T
never allows you to abandon the base value.
Addition . As an example of a type where this would be a problem, consider a type of type struct Handle(usize);
which is supported by a local array of stream resources. If the Drop
implementation for this type is launched in the wrong thread, this will result in it either making access outside the boundaries (where it is trying to destroy a resource that does not exist in this thread) or it will destroy the resource that is still in use.