Why Rc cannot be sent between threads
We get a compile error if we try to send Rc<T> to another thread:
use std::rc::Rc;
fn main() {
let rc = Rc::new(1);
std::thread::spawn(|| {
println!("{}", *rc);
})
.join();
}
error[E0277]: `Rc<i32>` cannot be shared between threads safely
--> src/main.rs:5:3
|
5 | std::thread::spawn(|| {
| ^^^^^^^^^^^^^^^^^^ `Rc<i32>` cannot be shared between threads safely
|
= help: the trait `Sync` is not implemented for `Rc<i32>`
= note: required because of the requirements on the impl of `Send` for `&Rc<i32>`
= note: required because it appears within the type `[closure@src/main.rs:5:22: 7:4]`
note: required by a bound in `spawn`
--> /home/bruno/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/mod.rs:625:8
|
625 | F: Send + 'static,
| ^^^^ required by this bound in `spawn`
For more information about this error, try `rustc --explain E0277`.
The compile error is triggered because the closure passed to std::thread::spawn must be Send. Types that implement Send are types that can be transferred across thread boundaries.
Rc primer
Rc<T> is a smart pointer that can be used to hold multiple references to T when T is owned by several objects. It kinda looks like a std::shared_ptr from C++ except it does not increment and decrement the reference count atomically, if you need thread safe reference counting in Rust take a look at Arc.
A value contained in an
Rc<T>will be dropped when the last reference to it is dropped. It is possible to create reference cycles and leak memory as well.
If we need a new reference to a T, the Rc<T> can just be cloned:
let a = Rc::new(1);
let b = Rc::clone(&a);
Rc internals
If we take a look at the Rc<T> source code we will see that it is actually kinda simple:
pub struct Rc<T: ?Sized> {
ptr: NonNull<RcBox<T>>,
phantom: PhantomData<RcBox<T>>,
}
?Sizedmeans the size ofTdoes not need to be known at compile-time. It’s fine to accept aTthat’s notSizedbecauseRc<T>isSized.
A Rc<T> pretty much boils down to a struct with two counters and a pointer to a value of type T. In the Rc<T> source code, the struct is called RcBox<T>:
// Note that Cell is used for internal mutability.
struct RcBox<T: ?Sized> {
/// How many references we have to this value.
strong: Cell<usize>,
// Weak ref? We'll ignore it for now.
weak: Cell<usize>,
/// The actual value
value: T,
}
When a Rc<T> is created, its strong count will be 1 because there is only one reference to the value inside of it. If we need more references (the point of using Rc<T>) we can just clone the Rc<T>
let a: Rc<String> = Rc::new(String::from("hello world"));
let b: Rc<String> = a.clone();
Cloning an Rc<T> means increasing its strong count and creating a copy of the RcBox<T>.
impl<T: ?Sized> Clone for Rc<T> {
#[inline]
fn clone(&self) -> Rc<T> {
unsafe {
// inner() returns the &RcBox<T> that's in the Rc<T> struct.
self.inner().inc_strong();
Self::from_inner(self.ptr)
}
}
}
inc_strong literally just increments the strong counter besides some safety checks:
#[inline]
fn inc_strong(&self) {
let strong = self.strong();
// We want to abort on overflow instead of dropping the value.
// The reference count will never be zero when this is called;
// nevertheless, we insert an abort here to hint LLVM at
// an otherwise missed optimization.
if strong == 0 || strong == usize::MAX {
abort();
}
self.strong_ref().set(strong + 1);
}
and from_inner just copies the pointer to RcBox<T>:
unsafe fn from_inner(ptr: NonNull<RcBox<T>>) -> Self {
Self { ptr, phantom: PhantomData }
}
After the clone, this is how things look like:

The strong count is decremented in the Rc<T> Drop implementation and the memory is freed if there’s no references left.
unsafe impl<#[may_dangle] T: ?Sized> Drop for Rc<T> {
fn drop(&mut self) {
unsafe {
self.inner().dec_strong();
if self.inner().strong() == 0 {
// destroy the contained object
ptr::drop_in_place(Self::get_mut_unchecked(self));
// remove the implicit "strong weak" pointer now that we've
// destroyed the contents.
self.inner().dec_weak();
if self.inner().weak() == 0 {
Global.deallocate(
self.ptr.cast(),
Layout::for_value(self.ptr.as_ref())
);
}
}
}
}
}
#[may_dangle]has to do with drop check
Why Rc is not Send after all?
Every time a Rc<T> is cloned, its strong count is incremented. If we had two or more threads trying to clone a Rc<T> at the same time, there would be a race condition since access to the strong count that’s in the RcBox<T> is not synchronized.

References
https://github.com/rust-lang/rust
https://doc.rust-lang.org/std/marker/trait.Send.html
https://doc.rust-lang.org/nomicon/send-and-sync.html
https://doc.rust-lang.org/std/rc/struct.Rc.html
https://doc.rust-lang.org/std/sync/struct.Arc.html
https://doc.rust-lang.org/std/ptr/struct.NonNull.html