use std::thread;
use std::rc::Rc;
fn main() {
let mut x = Rc::new("0".to_string());
let t = thread::spawn(move || {
println!("{}", *x);
});
}
result in the following error
error[E0277]: `Rc<String>` cannot be sent between threads safely
--> src/main.rs:6:13
|
6 | let t = thread::spawn(move || {
| _____________^^^^^^^^^^^^^_-
| | |
| | `Rc<String>` cannot be sent between threads safely
7 | | println!("{}", *x);
8 | | });
| |_____- within this `[closure@src/main.rs:6:27: 8:6]`
|
= help: within `[closure@src/main.rs:6:27: 8:6]`, the trait `Send` is not implemented for `Rc<String>`
= note: required because it appears within the type `[closure@src/main.rs:6:27: 8:6]`
note: required by a bound in `spawn`
I understand the direct reason is that Rc<> does not implement Send. But the key here is that the closure above has the move keyword before it. My Intuition is that it is safe to move anything into a new thread because nothing is shared between these threads. Is my intuition wrong or the Send/Sync mechanism just doesn't allow this correct code ?
The reference counter is shared, so the implementation of the increment-decrement mechanism has to be thread safe in order to use a refcounted pointer from multiple threads. That has runtime overhead which might be undesirable, so Rc doesn't use synchronization.
If you want a thread-safe refcounted pointer, use Arc.
In this particular case, though, there's no actual sharing: the Rc hasn't yet been cloned. Unfortunately, that sort of reasoning is too fine-grained for the type system to be able to handle.
A small refactoring to construct the Rc in the new thread lets the code work, however:
use std::thread;
use std::rc::Rc;
fn main() {
let x = "0".to_string();
let t = thread::spawn(move || {
let x = Rc::new(x);
println!("{}", *x);
});
t.join().unwrap();
}
As I understood, move in rust is just a shallow copy operation plus invalidating old value. In above code the closure has the move keyword before it, so when the closure is being created, the captured x is also created, by shallow copying and invalidating outer x. This all happens before new thread is spawn. Now there is only one single valid Rc<>, inside the closure, so nothing will be shared. Please point out my mistake.
In the general case, an owned Rc<T> likely has a shared reference count with other owned Rc<T> instances. A function like this cannot be valid, for instance, as x may have been cloned from some other Rc<String>:
Rust's type / trait system can't tell the difference between this and what you wrote, and so disallows your code to ensure that functions like spawn_print are always rejected.
But that isn't a property of Rc! The Rc type knows nothing about thread::spawn and it is completely agnostic of when any sort of independent function is called. You can't really encode that sort of property in the type system (at least not easily – you could do it with typestate AFAICT). All Send means is "is this type thread-safe?", for which the answer is "No, Rc is not thread safe".
Note that more generally, if you have an Rc<T> and you are sure that it has no other outstanding strong references, then you can use Rc::try_unwrap to extract the inner value T. If T: Send, then you can pass it into another thread.
In order to avoid the overhead of unwrapping and re-wrapping an Rc like that, one could also implement a safe wrapper-type for “uniquely owned Rcs”, e.g. like this:
Such a type can soundly implement Send, but also Sync or DerefMut, or an infallible into_inner method. It’s basically like a Box<T>, but cheaply convertible to/from Rc.