Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does `Arc<T>` require T to be both `Send` and `Sync` in order to be `Send`/`Sync` itself?

The 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 concurrently access the underlying T via an immutable reference &T. This is safe as long as T can't be modified in an unsynchronized manner via &T. This is true for all types with 'inherited mutability' (nearly all types) and false for the ones with unsynchronized 'interior mutability' (e.g. RefCell, ...).

As far as I understand it, the Send bound is not required here. For example, I think sharing my artificial type which implements Sync but not Send in an Arc is safe.

Lastly, &T itself also doesn't have this bound! 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 as Arc<T> allows the same access to T as &T does, I don't understand why Arc<T> has the additional Send bound. Why is that?

like image 571
Lukas Kalbertodt Avatar asked Jan 28 '17 12:01

Lukas Kalbertodt


People also ask

How does ARC work in Rust?

'Arc' stands for 'Atomically Reference Counted'. The type Arc<T> provides shared ownership of a value of type T , allocated in the heap. Invoking clone on Arc produces a new Arc instance, which points to the same allocation on the heap as the source Arc , while increasing a reference count.

What is arch length rust?

An integer the size of which is arch will be 32 bits on an x86 machine and 64 bits on an x64 machine.


1 Answers

I believe this is because an Arc owns the value it contains, and is thus responsible for dropping it.

Consider the following sequence:

  • A value of type T is created in thread 1. It is not Send, which means it is not safe to move this value to another thread.
  • This value is moved into an Arc handle.
  • A clone of the handle is sent to thread 2.
  • The handle stored by thread 1 is dropped.
  • The handle stored by thread 2 is dropped. Since this is the last handle, it assumes full ownership of the stored value and drops it.

And just like that, we've moved a value of type T from one thread to another, violating memory safety.

&T doesn't require Send because dropping a &T never allows you to drop the underlying value.

Addendum: As an example of a type where this would be a problem, consider a type like struct Handle(usize); which is backed by a thread-local array of resources. If the Drop implementation for such a type is run on the wrong thread, this will lead to it either doing an out-of-bounds access (where it tries to destroy a resource that doesn't exist on this thread), or destroys a resource that's still in use.

like image 55
DK. Avatar answered Sep 19 '22 12:09

DK.