I've searched for types that are Sync
, but not Send
, because it often looks like one trait is a superset of the other one (“every type that implements Sync
also implements Send
”). I've found this question, but the only real answer is really complicated.
So I've come up with this code:
struct Foo(Rc<()>); // <-- private field
impl Foo {
fn my_clone(&mut self) -> Self { // <-- mutable borrow
Foo(self.0.clone())
}
}
I know that the compiler won't automatically implement Send
nor Sync
for my type; but I'm interested in what I could safely implement manually. I think:
It should be able to implement Sync
: having an immutable reference to Foo
won't let us do anything with it (because we can only call my_clone()
via mutable/exclusive reference). And without doing anything, nothing can go wrong, right?
It should not be able to implement Send
: we can clone our Foo
in the main thread (before starting another thread) to get a second object. Now both objects share some memory (the reference count, stored in a Cell<usize>
). If I now could send one of those objects to another thread, both threads would have ownership of a Foo
, referencing the same memory. Thus both objects could call my_clone()
at the same time, leading to simultaneous, unsynchronized, mutable access to the reference count (a data race).
Is this reasoning correct or am I missing something?
Rc uses non-atomic reference counting. This means that overhead is very low, but an Rc cannot be sent between threads, and consequently Rc does not implement Send . As a result, the Rust compiler will check at compile time that you are not sending Rc s between threads.
A type is Send if it is safe to send it to another thread. A type is Sync if it is safe to share between threads (T is Sync if and only if &T is Send).
Send means that a type is safe to move from one thread to another. If the same type also implements Copy , this also means that it is safe to copy from one thread to another. Sync means that a type is safe to reference from multiple threads at the same time.
I know that the compiler won't automatically implement
Send
norSync
for my type.
Indeed, the compiler automatically implements Send
and Sync
for you, only when it can determine it is safe to do so.
This little program:
use std::cell::Cell;
use std::sync::atomic::AtomicUsize;
fn ensure_sync<T: Sync>(_: T) {}
struct Automatic(AtomicUsize);
impl Automatic {
fn new() -> Automatic { Automatic(AtomicUsize::new(0)) }
}
fn main() {
ensure_sync(AtomicUsize::new(0));
ensure_sync(Automatic::new());
ensure_sync(Cell::new(0));
}
Only errors out on the Cell::new(0)
line, Automatic
is Sync
because all its fields are Sync
.
Regarding Foo
, Rc
is neither Sync
nor Send
, so indeed the compiler will not implement either for you.
Can Foo
be Sync
?
I believe1 so. As long as NO other operation is added to the module that operate on immutable references. Now or in the future.
Can Foo
be Send
?
I agree with your conclusion, but I think you missed another method that modifies the Cell
: drop
.
So, indeed, you seem to have come up with a type that is Sync
and not Send
, by using an underlying type that is Send
and not Sync
. It may be my nerd sense, I find it quite amusing :)
1When dealing with unsafe
code, I am never sure of anything. It's very easy to fool oneself into thinking something is safe, simply because a tiny little detail escaped attention.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With