Discarding unused cloned Sender (Futures 3.0)


#1

I have a future-based client that sends messages over a future’s channel to a server.
The sender part of the channel is inside the client struct (MySender below).

I am trying to port some of my old futures code (1.x) to the latest Futures 3.0. The following is a simplified example of what I’m trying to do:

#![feature(futures_api)]
use futures::{Future, FutureExt, SinkExt};
use futures::channel::mpsc;

struct MySender {
    sender: mpsc::Sender<u32>,
}

impl MySender {
    fn send_num(&self, num: u32) -> impl Future<Output=()> {
        self.sender.clone().send(num).map(|_| ())
    }
}

My Cargo.toml:

[package]
name = "check_mutable_ref_send"
version = "0.1.0"
authors = ["real"]
edition = "2018"

[dependencies]

futures-preview = "0.3.0-alpha.7"

This code does not compile with Futures 3.0. It fails with the following compilation error:

$ cargo run
   Compiling check_mutable_ref_send v0.1.0 (/home/real/temp/check_mutable_ref_send)                                                                                                                                
error[E0716]: temporary value dropped while borrowed                                                                                                                                                               
  --> src/main.rs:11:9                                                                                                                                                                                             
   |                                                                                                                                                                                                               
11 |         self.sender.clone().send(num).map(|_| ())                                                                                                                                                             
   |         ^^^^^^^^^^^^^^^^^^^ creates a temporary which is freed while still in use                                                                                                                             
12 |     }                                                                                                                                                                                                         
   |     - temporary value is freed at the end of this statement                                                                                                                                                   
   |                                                                                                                                                                                                               
   = note: borrowed value must be valid for the static lifetime...             

In Futures 1.0 send() had the following signature (From the Sink trait):

fn send(self, item: Self::SinkItem) -> Send<Self> 

With the old send() signature, there was no problem compiling the line:
self.sender.clone().send(num).map(|_| ())

However, in Futures 3.0 send() was changed to have the signature:

fn send(&mut self, item: Self::SinkItem) -> Send<Self> 

While this signature seems less restricting (Only requires a mutable reference instead of taking ownership), I did not manage to find a way to make my old code compile.

Any ideas are appreciated!
real.


#2

My current workaround is:

/// A helper function to allow sending into a sink while consuming the sink.
/// Futures 3.0's Sink::send function takes a mutable reference to the sink instead of consuming
/// it. 
pub async fn send_to_sink<S, T, E>(mut sink: S , item: T) -> Result<S, E>
where
    S: Sink<SinkItem=T, SinkError=E> + std::marker::Unpin + 'static,
{
    await!(sink.send(item))?;
    Ok(sink)
}

I’m not very happy about it though. It feels to me like I’m using a lot of technology (async keyword and generators) to do something I could do very simply before only using combinators.


#3

You can avoid the helper function by using an async block:

#![feature(futures_api, async_await, await_macro)]
use futures::{Future, SinkExt};
use futures::channel::mpsc;

struct MySender {
    sender: mpsc::Sender<u32>,
}

impl MySender {
    fn send_num(&self, num: u32) -> impl Future<Output=()> {
        let sender = self.sender.clone();
        async move { await!(sender.send(num)).unwrap() }
    }
}

If you don’t want to be able to send multiple integers in parallel you can avoid both the async block and cloning the sender by using an async method:

#![feature(await_macro, async_await, futures_api)]
use futures::SinkExt;
use futures::channel::mpsc;

struct MySender {
    sender: mpsc::Sender<u32>,
}

impl MySender {
    async fn send_num(&mut self, num: u32) {
        await!(self.sender.send(num)).unwrap()
    }
}

For small examples like this async/await may be slightly more verbose than the old combinator based approach, but as soon as you start writing real-world code with it it really improves the readability of the code.


#4

@Nemo157: Thanks for your detailed reply. I didn’t think about the async block syntax!
It is interesting that in the second example a &mut self is required instead of just &self with the old combinator.