Why does try_send from crate futures require &mut self?

I've noticed that both std::sync::mpsc::Sender::send and tokio::sync::mpsc::Sender::try_send require only &self to work, but futures::channel::mpsc::Sender::try_send needs &mut self. I guess all of them must use some kind of synchronized interior mutability (am I right?) - then why is the futures variant more restrictive?

If I look at the source of futures::channel::mpsc::Sender::try_send, I believe it is setting the BoundedSenderInner::maybe_parked field (as is done i.e. here) that requires the Sender to be passed by mutable reference. I assume (haven't checked) that tokio and std's implementations just differ.

1 Like

Because their implementations are different, and these implementations have different tradeoffs.

With the futures channel, each sender has its own slot for the value it's going to send. Different slots can be used concurrently, but each individual slot can only be used by one thread at the time. To enforce that a sender's slot only has one user at the time, it requires mutable access.

On the other hand, Tokio's mpsc channel doesn't really have any per-sender data. Instead, when you call send, this creates a future that will store information related to the message being sent.

Interestingly, this design originates from the fact that the futures channel implements the Sink trait. The Sink trait is based on poll methods, rather than futures. When using poll methods, you don't have a future object to put state into, so everything must go into the sender instead.

It may be interesting to compare with PollSender. This is a wrapper around Tokio's mpsc sender that implements Sink, and indeed, PollSender also requires mutable access to send messages.