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>>,
}
?Sized
means the size ofT
does not need to be known at compile-time. It’s fine to accept aT
that’s notSized
becauseRc<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