It is not enough for a struct: Sync if all its fileds : Sync

struct SafeStruct {
    a: Mutex<i32>,
    b: Mutex<String>,
}

fn assert_sync<T: Sync>() {}
fn main() {
    assert_sync::<SafeStruct>();
}

The code proves SafeStruct : Sync when all its fileds : Sync
But that is not enough; a and b maybe related-field, and should be modified at same time.

SafeStruct: Sync makes it possible to modify single field at different threads in a conflict way.

T: Sync means safe code can't cause UB such as data races by sending &T: Sync to another thread, no more (and no less).

Preserving the invariants of a struct isn't a Sync-specific concern. That's something you have to bake into the public API and preserve in your private code.

That's true, but also, it is an important fact that one cannot simply take a data structure, replace RefCell with Mutex (or Cell with Atomic*), see it is now Sync, and conclude it is also correct under access from multiple threads.

I am wandering why not just disables this default behaviour
Force users to declare SafeStruct: Sync if they want it to be so.

Sync is an unsafe trait, so that would require unsafe impl in a lot of places.

Structs containing Mutex are not the typical case. The typical case is structs with no interior mutability at all, which should implement Sync if their contents do.

Arguably, Rust should have avoided the auto-trait system and had #[derive(Send, Sync)] — but then we would have a lot more errors where types are not marked thread-safe when they should be. It's a tradeoff.

2 Likes

In case you didn't know, you can opt-out with PhantomData.

2 Likes