CS3984 Computer Systems in Rust



Send and Sync

  1. Send and Sync are traits.
  2. Send and Sync are marker traits.
    • Marker traits are used to indicate to the compiler of some property.
    • Marker traits usually do not enforce any associated types or methods.
    • Example: Copy is a marker trait that tells the compiler that the type can be Cloned using a bitwise copy.
    • Example: Sized is a marker trait that tells the compiler that values of type T have a size known at compile time.
  3. Send and Sync are auto traits.
    • Auto traits are automatically implemented for types if possible.
    • For example, a struct T automatically implements Send if all the fields of T implement Send.

Send

  • A type T is Send if values of type T are safe to send to another thread.
  • Sending refers to transferring ownership, or moving the value to the new thread.
fn is_send<T: Send>(_val: T) {}

fn main() {
    let x = 8;

    std::thread::spawn(move || {
        dbg!(x);
    });

    is_send(x);
}


Most types are Send.

Sync

  • A type T is Sync if values of type T can be shared by other threads.
  • Sharing refers to accessing the value via reference among multiple threads.
fn is_sync<T: Sync>(_val: T) {}

fn main() {
    let x = 8;

    std::thread::scope(|s| {
        s.spawn(|| {
            dbg!(x);
        });
    });

    is_sync(x);
}


Send and Sync

T is Sync if and only if &T is Send.

fn is_send<T: Send>(_val: T) {
    println!("{} is Send", std::any::type_name::<T>());
}
fn is_sync<T: Sync>(_val: T) {
    println!("{} is Sync", std::any::type_name::<T>());
}

fn main() {
    let x = 8;

    is_sync(x);
    is_send(&x);
}


Not Send and Not Sync

  1. Raw pointers: *const T, *mut T
    • Semantic reason, up to programmer to ensure proper usage of the pointers.
  2. Reference counting pointer: Rc<T>
    • Not Send because updating the reference count is not atomic
    • Not Sync because Rc::clone takes &self, which updates the reference count

Not Sync

Types that are not Sync usually provide single-threaded interior mutability, like Cell<T> and RefCell<T>.

  • Send if T: Send.
  • Not Sync because interior mutability can be done with a &self.

use std::cell::Cell;

fn main() {
    let cell = Cell::new(5);

    std::thread::spawn(|| {
        cell.set(6);
    });

    std::thread::spawn(|| {
        cell.set(7);
    });
}


Not Send

The MutexGuard<T> type is

  • Sync if T: Sync.
  • Not Send because
    1. On certain operating systems, the std::sync::Mutex implementation uses POSIX threads (pthreads) mutexes under the hood.
    2. Pthread mutexes require unlocking a mutex on the same thread that locked the mutex.
    3. Since mutexes are unlocked in Rust by dropping the MutexGuard<T>, the guard cannot be sent to another thread.

Sometimes Send or Sync

The Mutex<T> type is

  • Send if T: Send.
  • Sync if T: Send because
    1. A Mutex<T> allows you to get a &mut T through a &Mutex<T>.
    2. We can take ownership through mutable references.
      • For example, Option<T> has pub fn take(&mut self) -> Option<T>.
      • If T: !Send, then Option<T>: !Send,
      • …so if Mutex<T> is still Sync, we can get a T on a different thread given a &Mutex<T>, which is a contradiction.

Question: Why does Mutex<T>: Sync not require T: Sync?

Sometimes Send or Sync (2)

The Arc<T> type is

  • Send if T: Send + Sync.
    • T: Sync is required because Arc<T>::deref(&self) returns a &T.
  • Sync if T: Send + Sync.
    • T: Send is required because

      pub fn try_unwrap(this: Arc<T, A>) -> Result<T, Arc<T, A>>
      
      
      

      Allows getting a T if this is the only strong reference left, moving T across threads.