Rust's `Sender/Receiver` is forced to be `Sync + Send`

pub struct AsyncTransporter {
    stream: Arc<dyn AsyncRW + Send + Sync>,

Suppose that a library expects AsyncTransporter to be Send + Sync, and that dyn AsyncRW is a struct that holds a channel tuple:

pub struct SmolSocket {
    channel: (Sender<Arc<Packet>>, Receiver<Arc<Packet>>),

How can I make it such that SmolSocket implements Send + Sync safely? Even though SmolSocket holds Sender and Receiver, it's going to be inside an Arc so I don't see how it could go wrong.

Unfortunately AsyncTransporter must be Send+Sync.

Context: AsyncTransporter is the connector in Builder in hyper::client - Rust so it automatically needs to be Send+Sync. At least this is what I think.

crossbeam-channel is a high-quality alternative to std::sync::mpsc, with Sender and Receiver types that are Send + Sync.

1 Like

thanks, but just for curiosity, what would be the solution in this case?

You can wrap them in the Mutex if you want to stick with the std::sync::mpsc.

I'm curious now, how can Mutex<Something> be Send + Sync? Is there somewhere unsafe impl Sync for Mutex<T> for all T?

This is from Hyper:

let client: Client<AsyncTransporter, hyper::Body> = Client::builder().build(connector);

it looks like connector must be Send + Sync. Looking at the signature: Builder in hyper::client - Rust it's not immediately clear why:

pub fn build<C, B>(&self, connector: C) -> Client<C, B> where
    C: Connect + Clone,
    B: HttpBody + Send,
    B::Data: Send,

If I look into Connect, at Connect in hyper::client::connect - Rust, we have

impl<S, T> Connect for S where
    S: Service<Uri, Response = T> + Send + 'static,
    S::Error: Into<Box<dyn StdError + Send + Sync>>,
    S::Future: Unpin + Send,
    T: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static,

it looks like Connect is implemented for S such that S is Send. Where the Sync comes from?

Check the requirement for the Send/Sync.

Sync means its shared reference can be shared between threads. Send means its ownership can be transferred between threads. Mutex<T> restricts mutually exclusive access to the T so Mutex<T> is Sync even the T is not. But it can be used as a channel between threads using std::mem::replace() or similars. Arc<T> provides a shared ownership to the underlying T. Sending Arc<T> into another thread allows multiple threads access the underlying T.

There's a reason Arc requires Sync + Sendto be sent to another thread:

  • This allows getting references to the inner value in multiple threads, hence the Sync bound
  • When the last one is dropped it may be in another thread, in which case the inner value is actually sent to another thread, hence the Send bound.

Sender not being Sync means that if two threads had a shared reference to it they could cause a data race. It being in an Arc doesn't solve anything, this problem is still there, and it's even easier to trigger (if there wasn't the Sync restriction)

Sender not being Sync is an intrinsic problem of the standard's mpsc module. As already mentioned there are better alternatives like crossbeam or flume

That feels pretty wasteful... Why use a channel if you're going to lock it.

The way to solve this while keeping the std's Sender would be to clone the Sender instead of wrapping it in an Arc and cloning that.

The way to solve this while keeping the std's Senderwould be toclonetheSenderinstead of wrapping it in anArc and cloning that.

But on the line

let client: Client<AsyncTransporter, hyper::Body> = Client::builder().build(connector);

I must pass a connector: AsyncTransporter. This connector must hold dyn AsyncRW. The dyn AsyncRW must hold a channel.

I think that the mere fact that dyn AsyncRW must hold a channel, forces it to be not Sync (or not Send, I don't know exactly). This forces AsyncTransporter to be not Sync or Send, so there's no cloning that could help here, I think. Is there a way?

Hi...for what reason does Arc have such solid prerequisites for T? T likewise needs to carry out Send in light of the fact that Arc can behave like a holder; in the event that you could simply shroud something that doesn't execute Send in an Arc, send it to another string and unload it there... awful things would occur. The intriguing part is to perceive any reason why T likewise needs to carry out Sync.

Consider the following list of things you can do with a value:

  1. Mutably access it from threads other than the one it was created in. (this includes dropping it)
  2. Immutably access it from threads other than the one it was created in.
  3. Immutably access it from multiple threads at the same time.

The Send and Sync traits correspond to these in the following way:

  • !Send + !Sync none of them are allowed
  • Send + !Sync 1 and 2 are allowed
  • !Send + Sync 2 and 3 are allowed
  • Send + Sync 1, 2 and 3 are allowed

However, if you are able to perform 2 on an Arc<T>, then you can perform all of 1, 2 and 3 on the inner T value in the following ways:

  • To perform 1, you perform a clone of the Arc<T> in some other thread. Then you destroy the original Arc<T>. You can now perform 1 on the inner T by calling get_mut on the clone.
  • To perform 2, just use the Deref impl on the Arc<T> in some other thread.
  • To perform 3, use cloning to get an Arc<T> in several threads. Then use the Deref impl on each clone to access the inner T from several threads at once.
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.