In Rust, Send and Sync are two key traits that define how data can be safely shared or moved between threads.

Send: Moving Ownership Across Threads

A type is Send if it can be safely transferred from one thread to another and Rust automatically derives Send for most types, as long as all their components are Send.

use std::thread;
 
fn main() {
    let data = String::from("Hello, world!");
 
    let handle = thread::spawn(move || {
        println!("{}", data);
    });
 
    handle.join().unwrap();
}

Here, String is Send, so it can be moved safely to the new thread.

Sync: Sharing References Across Threads

A type is Sync if it is safe to access references to it from multiple threads simultaneously. Most primitive types (e.g., integers, floats) are Sync because they are immutable or use atomic operations internally.

Example: A Sync Type

use std::sync::Arc;
use std::thread;
 
fn main() {
    let data = Arc::new(5);
 
    let handles: Vec<_> = (0..5).map(|_| {
        let data_clone = Arc::clone(&data);
        thread::spawn(move || {
            println!("{}", *data_clone);
        })
    }).collect();
 
    for handle in handles {
        handle.join().unwrap();
    }
}

Here, Arc is Sync because multiple threads can hold immutable references to the same value.

Relationship Between Send and Sync

  • If a type is Sync, it means its references are Send. In other words, &T is Send if T is Sync.
  • However, a type can be Send but not Sync, meaning it can be transferred between threads but not accessed concurrently.
What It EnsuresExample Types
SendOwnership can move across threadsString, Vec, Arc<Mutex<T>>
SyncReferences can be shared across threads&i32, Arc<T>

What If the Arc Is Send But Not Sync?

If the Arc is Send but not Sync, it means the Arc itself can be moved across threads, but references to its contents cannot be shared safely between threads. It is up to

  1. Exclusive Access:

    • Only one thread at a time can own the Arc.
    • The thread that owns the Arc can access its contents, but it cannot share references with other threads.
  2. Practical Implications:

    • You can transfer ownership of the Arc between threads, but you must ensure that no two threads access the contents concurrently.
  3. Example:

    • Suppose the inner type is a Mutex:
use std::sync::{Arc, Mutex};
use std::thread;
 
fn main() {
    let data = Arc::new(Mutex::new(5));
 
    let handle = thread::spawn(move || {
        let mut value = data.lock().unwrap();
        *value += 1;
    });
 
    handle.join().unwrap();
 
    let value = data.lock().unwrap();
    println!("Updated Value: {}", *value);
}

Here, the Mutex ensures that even though the Arc is Send but not Sync, access to the data remains safe through locking.