Does "sending to another thread" imply making all side effects visible?

For example, thread A sends an object T to thread B. Does everything that happens in thread A (before the sending) become visible to thread B (when thread B receives T)?

As a concrete example, can this fail:

use std::sync::atomic::{AtomicUsize, Ordering};

static X: AtomicUsize = AtomicUsize::new(0);

struct T {
    x: usize,

fn thread1(t: T) {
    // t.x has some arbitrary value, but X equals 0, Ordering::Relaxed); // No ordering on X itself
    // Now send t to thread2

fn thread2(t: T) {
    // Received t from thread1
    assert_eq!(X.load(Ordering::Relaxed), t.x); // <<< Can this ever fail?

Note I'm not talking about any specific channel implementation. I'm asking for what being Send generally implies (IOW, what any code sending an object is required to do). Any help is appreciated!

Obviously, if fn thread1(t: T) { … } and fn thread2(t: T) { … } are supposed to represent different threads, then when thread2 executes before thread1 the value of t.x is undefined. send does not imply magic initialization or presume any temporal ordering of execution of the threads.

What is "send" in this case? There isn't a single operation for "sending to another thread". What in particular are you using to do this?

If the particular operation has an operation order, that could have implications on X. But there's no way to know without knowing what operation you're using to send.

In and of itself, Send makes absolutely no guarantees. Instead, it's a convention that Send means moving a value to a different thread of execution and using it there will still be valid.

We use this pervasively throughout the standard library and ecosystem to ensure thread-safety and correctness, but at the end of the day it's just a convention that writing Foo: Send means Foo is safe to be passed to another thread. Likewise, writing unsafe impl Sync for Foo is a declaration from the programmer that Foo is safe to be accessed concurrently.

That means if ordering of memory accesses is important to your code being correct/free of data races, it's your responsibility to make sure operations are done correctly. In this case, you need to use the correct Ordering otherwise you'll have race conditions (note that race conditions and data races aren't quite the same thing).

Generally all code that has exclusive access to a value, through either ownership or mutable reference, must have a happens-after relationship between any such other code. I don't think it's possible to implement a thread send with this property that doesn't use acquire/release in a way that would make X properly transferred too, but I don't think it has been specified that this is required.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.