My question can also be seen as: If T is sent between two threads, is something like memcpy invoked?
Send but not
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 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
Here's a way to think about the difference between these two markers:
- A thing that is
Sendhas only one owner at a time, but ownership can be transferred across threads
- A thing that is
Copycan 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
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
Drop) and the basic memory image can just be copied and later freed with no side-effects. For more: https://doc.rust-lang.org/book/appendix-03-derivable-traits.html?highlight=copy#clone-and-copy-for-duplicating-values
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.
Sync means that a value can be shared across threads.
Send is best understood by a negative definition: a type is
it is a shared reference (e.g.,
&T) to a non-
T : !Sync,
Tmay not be sound to share across threads, and since sending a shared reference to a
&T) to another thread would allow to share the
Tacross threads, it is forbidden.
- note that
&Tis not the only "shared reference" to a
Tthat can exist,
&&Tis also a shared reference to a
T, as well as
- note that
Droplogic 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]
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 of
const): 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 the
Synctrait is then for: a compile-time check that forbids (in Rust) to have such references exist in multiple threads.
RcBox<_>(the heap structure with counter metadata an
Rcpoints to) both use an underlying
Cell, so they are transitively not
Rc<T>is a form of shared reference to a
RcBox<_>, and the latter is not
a counter-example of this
Synccounter-example would be something like
Mutex<_>: 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. Thus
Mutex<T> : Sync(when
T : 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
Sync properties of the type being "forgotten" by the compiler. For instance,
Box<i32> is a type that is both
Sendable across threads, but if
Box<i32> gets coerced to
Box<dyn Display>, the latter is not
Sendable across threads (if both traits are important, it "should" be coerced to
Box<dyn Display + Send>).