If you have an async fn foo(arguments…) {}
then arguments…
are always captured by the future this produces. The future that an async fn
generates is always such that when you call the async fn
initially nothing happens, and it only starts executing when the future is awaited; so all arguments need to be captured.
As a consequence, even if you comment out the actual to Sender::send call completely, your code still doesn’t work if this async fn send
is used.
async fn send<S: Sender>(sender: &S, s: String) {
// sender.send(s).await;
}
fn send(&self, s: String) -> impl Future<Output = ()>
can be different than this. It’s the same signature as an async fn
, but doesn’t force the implementor to have its whole body wrapped in an async { … }
block [that’s what’s actually creating this “do nothing until awaited” thing].
This is why fn send(&self, s: String) -> impl Future<Output = ()> + Send
can make sense in the first place: an implementation (with a !Sync
Sender) can be made work by first converting the &self
into something that is actually Send
, then wrapping only that into a Future
. Calling this send
function must be doing a little bit more work immediately when called, not just wrap the arguments and defer all computation until the await
starts.
Now, if // Some additional logic will be here.
will include additional logic including .await
usage, you’re probably out of luck. Let’s see why with some tests in the calling function.
As you noted, this works
async fn run<S: Sender>(sender: S, mut ch_receiver: mpsc::Receiver<String>) {
loop {
match ch_receiver.recv().await {
Some(s) => {
// This works
sender.send(s).await;
}
None => {
break;
}
}
}
}
and doesn’t need to capture any &S
because it owns the sender: S
. Calling sender.send(s)
creates a future from the &sender
reference. The reference is then no longer needed and the compiler understands that. Only after that, the .await
starts.
This similarly, it works with
Some(s) => {
// Some additional logic *with* awaits
async {}.await;
// This works
sender.send(s).await;
}
This works because the borrow of sender
is only created after the previous .await
s are all done. But if instead you do:
Some(s) => {
let sender_ref = &sender;
// Some additional logic *with* awaits
async {}.await;
// This doesn't work anymore now
sender_ref.send(s).await;
}
then the compiler complains again. Once again, the call to .send
is actually irrelevant:
Some(s) => {
let sender_ref = &sender;
// Some additional logic *with* awaits
async {}.await;
// This doesn't work either
sender_ref;
}
Thus, as you can see, if it isn’t even possible inline to do additional logic with .await
s while the &sender
reference already exists, abstracting this into a function with a sender: &S
argument of course is impossible, too.
Now… if additional logic
does not need any .await
though, then you can solve the issue; as first laid out above, you can use a fn … -> impl Future
approach, and implement it in a way that does any prior additional logic
eagerly, then return the future without capturing the &sender
:
fn send<S: Sender>(sender: &S, s: String) -> impl Future<Output = ()> + use<'_, S> {
// Some additional logic; eagerly executed, can't use .await
// …
// finally, just return the future without additional `async { ….await }` wrapping
sender.send(s)
}
(playground)