Skip to content

select! and Option<Future<…>> #2270

@valpackett

Description

@valpackett

A common situation in my current project is that I need to select! between the results of calling async methods on various things that might not exist. Unfortunately the select macro doesn't seem to have any native support for conditionally including branches in the select, something that would be like

futures::select! {
  if let Some(ref mut obj) = maybe_existing_object {
    event = obj.async_method().fuse() => do_something_with(event).await
  },
  event = always_existing_object.async_method().fuse() => do_something_with(event).await,
}

And it's not possible to do maybe_obj.map(|o| o.async_method().fuse()).unwrap_or_else(|| future::Fuse::terminated()) (or future::pending() etc.) because, well, async methods — that never-resolving future we use for when the object doesn't exist doesn't match the impl Future opaque type of the async function, so they're incompatible unless we go with the boxing and dyn route and all that inefficiency and sadness.

So far this is the best thing I could come up with:

pub struct MaybeFuture<F>(Option<F>);

impl<F> MaybeFuture<F> {
    pub fn new(f: Option<F>) -> MaybeFuture<F> {
        MaybeFuture(f)
    }
}

impl<F: Future + Unpin> Future for MaybeFuture<F> {
    type Output = F::Output;

    fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll<Self::Output> {
        // XXX: unchecked should be fine here, is it faster than Unpin?
        if let Some(ref mut f) = self.get_mut().0 {
            Future::poll(Pin::new(f), cx)
        } else {
            task::Poll::Pending
        }
    }
}

impl<F: FusedFuture + Unpin> FusedFuture for MaybeFuture<F> {
    fn is_terminated(&self) -> bool {
        if let Some(ref f) = self.0 {
            f.is_terminated()
        } else {
            true
        }
    }
}
        futures::select! {
            ev = this.keyboard_events.select_next_some() => this.on_keyboard_event(ev).await,
            ev = MaybeFuture::new(this.ptr.as_mut().map(|p| p.next())) => this.on_pointer_event(ev).await,
            ev = MaybeFuture::new(this.touch.as_mut().map(|p| p.next())) => this.on_touch_event(ev).await,
            // ......
        }

Maybe there should be direct impl<F: Future> Future for Option<F> like this? Or native syntax for conditionally including select! branches depending on Options being Some or None (but that might be more difficult)?

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions