I was recently confused by a compiler error while writing a multi-threaded program in Rust.

Problem

In short, I have a structure with an internal Rc value, as follows.

1
2
3
struct A {
    val: Rc<u32>,
}

Obviously, Rc is not thread-safe, so it is protected by a lock and passed between threads with Arc, so the following code is written.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fn main() {
    let mutex = Mutex::new(A { val: Rc::new(5) });
    let target = Arc::new(mutex);

    let t = thread::spawn(move || {
        target.lock();
        // do something...
    });

    t.join().unwrap();
}

However, the compiler’s throws an exception.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
error[E0277]: `std::rc::Rc<u32>` cannot be sent between threads safely
   --> src/main.rs:13:13
    |
13  |     let t = thread::spawn(move || {
    |             ^^^^^^^^^^^^^ `std::rc::Rc<u32>` cannot be sent between threads safely
    |
   ::: /home/zinglix/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/mod.rs:593:8
    |
593 |     F: Send + 'static,
    |        ---- required by this bound in `std:🧵:spawn`
    |
    = help: within `A`, the trait `std::marker::Send` is not implemented for `std::rc::Rc<u32>`
    = note: required because it appears within the type `A`
    = note: required because of the requirements on the impl of `std::marker::Send` for `std::sync::Mutex<A>`
    = note: required because of the requirements on the impl of `std::marker::Send` for `std::sync::Arc<std::sync::Mutex<A>>`
    = note: required because it appears within the type `[closure@src/main.rs:13:27: 16:6 target:std::sync::Arc<std::sync::Mutex<A>>]`

Simply put, Rc in A does not implement Send, but this makes people confused, I add locks so that things that are not thread-safe can be shared between threads, but why do I need to implement Send?

Reconceptualizing Sync and Send

Sync and Send are very important foundational concepts in Rust multithreading.

  • Sync means that sharing between threads is safe.
  • Send means that passing to another thread is safe.

Sync indicates that different threads can use the same object at the same time, for example, by reading the same constant at the same time. For a lock Mutex, the Sync trait is satisfied because several threads can execute lock() on the same lock at the same time, except that the internal mechanism of the lock will only allow only one thread to obtain the lock at the same time, but this does not matter, its external performance is satisfied. So for variables, a layer of Mutex is usually sufficient to allow them to be shared between threads.

Send means that ownership can be passed between threads, and different threads can use the same object at different times. Thread A can create an object and then pass (send) it to thread B. Because of the ownership mechanism, thread B can then use the object and A cannot because ownership has been passed to B. However, if the object implements Clone, then a copy can be made to another thread. The Mutex does not implement the Clone trait, so it is common to use Arc<Mutex> to allow multiple threads to have the same lock at the same time.

It is worth mentioning that Send and Sync are flagged traits, meaning that they do not require any method implementation, and whether a structure satisfies them is manually flagged, not determined by the compiler, except for the condition that all members satisfy the trait, which is inferred from the fact that the structure also satisfies the trait. unsafe code, which cannot be protected by the compiler at implementation time and needs to be implemented carefully to ensure safety.

So it is easy to see that since it is safe to use at the same time (Sync), it must also be safe to use at different times (Send), after all, it cannot be passed to other threads, so how can several threads use it at the same time? So almost no object will be satisfied with Sync and not Send.

Back to the question

Let’s take a look at the definition in Mutex.

1
2
3
4
#[stable(feature = "rust1", since = "1.0.0")]
unsafe impl<T: ?Sized + Send> Send for Mutex<T> {}
#[stable(feature = "rust1", since = "1.0.0")]
unsafe impl<T: ?Sized + Send> Sync for Mutex<T> {}

You can see that Mutex<T> can indeed add a Sync trait to all types of T, but only if T satisfies Send. Why? Because Mutex must satisfy the Send trait if it wants to implement Sync, and then it also passes ownership of T, so T needs to satisfy Send as well.

The fundamental problem is that Rc doesn’t satisfy Send, so Mutex can’t implement Send, which then creates a series of unsatisfiable problems that eventually lead to the failure to pass between threads.

Why doesn’t Rc satisfy Send? Send means that it is safe to pass between threads, but for Rc, if I have multiple copies of Rc in the current thread and pass one to another thread, then multiple threads own the object, but the reference counting operation in Rc is not thread-safe, so Rc does not satisfy Send, which means that Rc can only be passed to one thread during its entire can only be owned by one thread for its entire life cycle.

So why doesn’t Rc work even if it is locked with Mutex<Rc> so that multiple threads can’t operate on it at the same time? The ostensible reason is that Mutex requires T to implement Send. To take this a step further, imagine that you can make a copy of Rc after obtaining the lock, and then take this copy out of the lock’s scope, so that several threads can modify the reference count at the same time, and the lock you’re envisioning is null and void, and Rust successfully saves you from that.

Hmm? You said Mutex<Arc> would also have this problem? The Arc copy is thread-safe and doesn’t cause problems.

What? You’re saying you can make a copy and have multiple threads modify it at the same time? A copy of Arc is brought out from the lock, but Arc doesn’t give you a mut ref, and there is no way to modify it. Really want to modify it? Put a Mutex on it.

Summary

The fundamental problem is that Rc is not safe to pass between threads, so you can just replace it with Mutex<Arc>.