Sync but not Send?

It’s a relatively simple question (though maybe with not so simple answer), mainly out of curiosity and desire to understand intrinsincs better.

We have several types which are Send but not Sync (this is, for example, every Cell-like struct). Reasoning is easy to understand: when the type has interior mutability, we must be sure that we mutate it from one place only, but this place can be everywhere as long as it is singular.

But is there any case where the opposite would be sound - i.e., type is Sync and not Send? Seems that this will mean that it must have exactly one owner (some complex cloning semantics, like Rc?), but can have concurrent borrowers. I can’t imagine this for now, but maybe someone has encountered this already?

6 Likes

I’ve wondered that in the past as well!

I’ve heard that there are some ffi types which must be destroyed on the same thread on which they are created, but I can’t give a concrete example.

A contrived example would be a struct which, on creation, puts something to the thread local storage, and accesses that info in Drop.

Here’s a concrete example using the “thread local storage in Drop” use case: OpenSpan<Attached>.The crate tracks the current tracing context in thread local storage and restores the previous on drop, so it can’t be moved across threads.

9 Likes

It’s interesting, then, that docs clearly state that Send is implemented for the struct, but description text says otherwise, and source code also lead to conclusion that it is not Send. Bug in cargo doc or some unhandled corner case?..

It says that Send is implemented for OpenSpan<T> when T implements Send. Attached does not implement Send.

1 Like

I think I’ve found another interesting theoretical example.

One can imagine a thread-local allocator which doesn’t have any locks, but requires alloc/dealloc happen on the same thread.

Using a Sync +!Send ZST, it would be possible to pin types like Vec<T, LocalAlloc> to a single thread, once we have custom allocators.

Another example is structs that only have &mut self methods. An & reference to it is completely opaque, so there's no harm if it is sent to another thread.

3 Likes

But this struct would generally be Send as well, no?

It might not be. Maybe it uses Rc internally.

2 Likes

Let me emphasize that this point should not be underestimated / discarded as "a rare theoretical concept"!

Indeed, when dealing with async-created Send + Futures, it may sometimes happen that Rust complains "too much".

That's where, for instance, @Nemo157's Unshared wrapper can come in handy:

With which one can come up with a (yes, contrived), example of !Send + Sync type: Playground

3 Likes

It's also worth mentioning that MutexGuard, RwLockReadGuard and RwLockWriteGuard are like this.

3 Likes