Skip to content

Cannot ergonomically match between diverging futures #683

Closed
@mqudsi

Description

@mqudsi

I'm trying to come up with a good issue that describes the various type incompatibility problems I've run into with futures-rs in the past. This is a very simple and contrived use case, but I think it demonstrates the basic problem.

I don't think there's an ergonomic way of handling a case where, depending on the result of one action the called futures undergo different transforms that arrive at the same result.

Here's the code (git repo here: https://git.neosmart.net/mqudsi/futuretest/src/branch/futures-rs-683)

#![feature(conservative_impl_trait)]

extern crate futures;
extern crate tokio_core;
use futures::future::{self, FutureResult};
use futures::future::*;
use tokio_core::reactor::Core;

#[derive(Debug)]
enum ErrorCode
{
    CanHandleWithAsync,
    CanHandleWithSync,
    CannotHandle
}

#[derive(Debug)]
enum ErrorCode2
{
}

fn main() {
    let mut core = Core::new().expect("Failed to initialize tokio_core reactor!");

    let f = async_entry_point()
        .or_else(move |err| {
            //the problem is that the three matches below resolve to different future types
            match err {
                ErrorCode::CanHandleWithAsync => async_error_handler()
                    .map_err(|_| ErrorCode::CannotHandle),
                ErrorCode::CanHandleWithSync => future::result(sync_error_handler()),
                ErrorCode::CannotHandle => future::err(ErrorCode::CannotHandle),
            }
        })
    ;

    core.run(f).unwrap();
}

fn async_entry_point() -> FutureResult<(), ErrorCode> {
    future::ok(())
}

fn async_error_handler() -> FutureResult<(), ErrorCode2> {
    future::ok(())
}

fn sync_error_handler() -> Result<(), ErrorCode> {
    Ok(())
}

which returns the following compiler error:

   Compiling futuretest v0.1.0 (file:///mnt/d/GIT/futuretest)
error[E0308]: match arms have incompatible types
  --> src/main.rs:28:13
   |
28 | /             match err {
29 | |                 ErrorCode::CanHandleWithAsync => async_error_handler()
30 | |                     .map_err(|_| ErrorCode::CannotHandle),
31 | |                 ErrorCode::CanHandleWithSync => future::result(sync_error_handler()),
32 | |                 ErrorCode::CannotHandle => future::err(ErrorCode::CannotHandle),
33 | |             }
   | |_____________^ expected struct `futures::MapErr`, found struct `futures::FutureResult`
   |
   = note: expected type `futures::MapErr<futures::FutureResult<_, ErrorCode2>, [closure@src/main.rs:30:30: 30:57]>`
              found type `futures::FutureResult<_, ErrorCode>`
note: match arm with an incompatible type
  --> src/main.rs:31:49
   |
31 |                 ErrorCode::CanHandleWithSync => future::result(sync_error_handler()),
   |                                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to previous error

error: Could not compile `futuretest`.

To learn more, run the command again with --verbose.

Normally, my workaround would be to use future::result() to force parallel construction of the futures, but since in this case two of the error cases can be resolved immediately but the third requires the evaluation of another future, the end result is trying to compare a single-level future (FutureResult<_, ErrorCode>) with a multi-level future (MapErr<FutureResult<_, ErrorCode>>), which obviously causes the type error above.

In terms of solutions that would make this more ergonomic, what comes to my mind is if the return type of all the future transforms were unified as members of an enum wrapped in an opaque struct rather than unified via implementation of a shared trait (especially since impl trait is pretty useless as it currently stands). I'm sure there's a catch there and it's not as simple as I'm thinking it is, but there has to be a better way than this. In its current state, futures-rs becomes very hard to use when dealing with what is essentially a state machine (which is ironic since rust itself is particularly well suited for state machine composition with its powerful pattern matching) as the different responses each trigger the evaluation of differing futures.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions