My question can also be seen as: If T is sent between two threads, is something like memcpy invoked?
String
is Send
but not Copy
Proof by contradiction, I like it
Context switching occurs, but the memory doesn't have to change position... that makes sense
For more information, Send
means you can send the ownership of this type between threads. Practically most types are Send
, like the String
allocated in this thread, sent to that thread, and can be deallocated there. One example of the non-Send
type is Rc
. Rc
modifies its reference counter in non-atomic way, so if two instance of Rc
that points to same memory got cloned in two different threads concurrent, it triggers data race.
memcpy's involvement is an implementation detail, and does not play (much of) a role in interpreting semantics of Copy
when using the language.
Another case is memcpy can still occur even under move semantics, as long as it looks like a move at the higher level.
EDIT: memcpy and Copy
are not very related is what I was trying to say poorly.
In Rust, move is semantically memcpy()
out the value, invalidating previous one at compile time if it's not Copy
.
Here's a way to think about the difference between these two markers:
- A thing that is
Send
has only one owner at a time, but ownership can be transferred across threads - A thing that is
Copy
can be duplicated as well as borrowed, so that multiple owners can each have their own and simplify management.
Phrased as above, there's very little relationship between the two. The one with a more meaningful relationship to Send
is Sync
, which is (roughly) about borrowing across threads.
This copy is semantically the same as memcpy()
in that the constraint is that no other code needs to be run (that's Clone
and Drop
) and the basic memory image can just be copied and later freed with no side-effects. For more: C - Derivable Traits - The Rust Programming Language
The common case is that Copy
is typically used for 'trivial' basic types (like integers) used in assignments or passed as in-practice-immutable arguments to functions, to avoid forcing the programmer to always make them immutable references rather than lose ownership.
You can mark your own more complex types as Copy
(subject to various constraints) but in practice you very rarely need or want to. For the most part, you can think of it as something that enables better optimisations both in the compiler and in code readability.
More accurately, Sync
means that a value can be shared across threads.
Send
is best understood by a negative definition: a type is Send
unless:
-
it is a shared reference (e.g.,
&T
) to a non-Sync
type; sinceT : !Sync
,T
may not be sound to share across threads, and since sending a shared reference to aT
(a&T
) to another thread would allow to share theT
across threads, it is forbidden.- note that
&T
is not the only "shared reference" to aT
that can exist,&&T
is also a shared reference to aT
, as well asRc<T>
andArc<T>
, etc.
- note that
-
its internal
Drop
logic involves some thread-local state (quite rare to be honest, but not impossible), or its very existence involves some thread-local property (e.g., a thread-local unique integer).
So the real question ends up being: when is a type [not] Sync
?
A type cannot be Sync
if it is unsound to share it across threads. This is the case, for instance, when:
-
it offers "unchecked" / unsynchronised interior mutability / aliased mutability.
-
A good example of it is
Cell<_>
: if you have a&Cell<i32>
, then this reference acts pretty much as a C++int32_t &
reference (notice the lack ofconst
): it can be mutated despite the pointer being aliased, which is not multithread-safe since nothing at runtime protects the mutation from being racy. That's what theSync
trait is then for: a compile-time check that forbids (in Rust) to have such references exist in multiple threads. -
RefCell<_>
andRcBox<_>
(the heap structure with counter metadata anRc
points to) both use an underlyingCell
, so they are transitively notSync
either.- Since
Rc<T>
is a form of shared reference to aRcBox<_>
, and the latter is notSend
,Rc<_>
is neitherSend
norSync
,
- Since
-
a counter-example of this
Sync
counter-example would be something likeMutex<_>
: this wrapper does offer mutation of the wrappee through a shared / aliased reference&Mutex<_>
, but it does so by using a lock that guarantees at runtime that the critical section where the mutation takes place has exclusive access, so no data race here. ThusMutex<T> : Sync
(whenT : Send
).
-
-
the type's shared logic (API using
&T
) involves some thread-local state (again, quite rare).
Finally, as with any marker trait, trait object type erasure (dyn ...
) can lead to the Send
/ Sync
properties of the type being "forgotten" by the compiler. For instance, Box<i32>
is a type that is both Display
able and Send
able across threads, but if Box<i32>
gets coerced to Box<dyn Display>
, the latter is not Send
able across threads (if both traits are important, it "should" be coerced to Box<dyn Display + Send>
).
This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.