How to avoid boxing futures creted in iterator

How to make commented code work.

use futures::FutureExt;

#[derive(Clone)]
struct Request;
struct Client;

impl Client {
    pub async fn reqest(&self, _req: Request) -> Result<(),()> { Ok(()) }
}
async fn foo(req: Request) {
    let clients = vec![Client,Client,Client,Client];

    //let reqest = clients.iter().map(|client| client.reqest(req.clone()));
    let reqest = clients.iter().map(|client| client.reqest(req.clone()).boxed());
    
    futures::future::select_ok(reqest).await.unwrap();
}

Link to Play
If uncomment code rust say that Future cannot be unpinned. it seems that there should be some kind of trick that will allow you not to make an additional allocation.

select_ok in futures::future - Rust requires that the future implement Unpin. But the anonymous type created by async functions implement !Unpin. The only solution is to use Box.

source

I found another option, but this still require allocation. And Pin::new_unchecked

let mut requests = clients
    .into_iter()
    .map(|c| {
        let req = req.clone();
        async move { c.requests(req.clone()).await }
    })
    .collect::<Vec<_>>();

    let requests = requests
        .iter_mut()
        .map(|requests| unsafe { Pin::new_unchecked(requests) });

Play

Note that this dangerously relies on Vec not moving its items before dropping.

But vector can be dropped only if future resolved or dropped.
So I can't see any UB here.
And also you can't move vector because

requests
        .iter_mut()

create unique reference. And this reference hold by iterator.

And this code just example of approach. Look like we should have some trick to wrap item produced by iter() into Pin.

Imagine that Drop for Vec was implemented by doing vec.into_iter().for_each(drop). This first moves the Futures and only after that it drops them. This would break the Pin guarantee since the Futures were moved before dropping it!

No, the vector is the one that drops the Futures.

This is true but it doesn't prevent the issue: when the Vec is automatically dropped at the end of the scope it could do anything to the contained Futures, potentially breaking the Pin guarantees.

I don't think this is generally sound.

2 Likes

I'm no unsafe expert, but I believe the Vec issue can be resolved by collecting into Pin<Box<[_]>> instead:

async fn foo(req: Request) {
    let clients = vec![Client, Client, Client, Client];

    let mut requests = Box::into_pin(
        clients
            .into_iter()
            .map(|c| {
                let req = req.clone();
                async move { c.requests(req.clone()).await }
            })
            .collect::<Box<[_]>>(),
    );

    let requests = unsafe {
        Pin::get_unchecked_mut(requests.as_mut())
        .iter_mut()
        .map(|req| Pin::new_unchecked(req) )
    };

    futures::future::select_ok(requests).await.unwrap();
}

Ideally, there'd be something like impl IntoIterator<Item=Pin<&'a mut T>> for Pin<&'a mut [T]> in the standard library to remove the need for explicit unsafe here, but I couldn't find one.

3 Likes

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.