Futures chaining try to return impl Future but failed


#1

Hi guys, I’m building a simple Http Client based on Actix framework hosted on https://github.com/songday/httpclient

Currently, project compiles fine,
but if change lib.rs (https://github.com/songday/httpclient/blob/master/src/lib.rs) to use client2 mod and then compile, compiler says:
expected struct futures::FutureResult, found struct futures::AndThen

the only differences are:
functions in client mod return Box<Future<Item=String, Error=MyError<'a>>>
functions in client2 mod return impl Future<Item=String, Error=MyError<'a>>

I found this post https://stackoverflow.com/a/48082843 explains why Box wrapper works
then found this post Future chaining says impl Future should also work

so I confused, why my approach doesn’t work?


#2

impl Trait only works when there is a single data type internally. Your code tries to return two that are different. So need Box or a custom enum that implements Future for the two types (but this is much more work.)
Note: with Box inside you can still use impl Trait, just need to cast.
return Box::new(err(MyError("Error::InvalidUrlArgument"))) as Box<dyn Future<Item=_, Error=_>>


#3

There’s an Either type in the futures crate that can be used to return one of two possible types.


#4

Hi Jonh, thank you for your reply.
Could you please be more specific on Your code tries to return two that are different, could you point out which two that I try to return?

I changed client2 mod as following:

use std::time::Duration;
use std::fmt::Write as FmtWrite;
use std::io::Error;

use url::percent_encoding::{percent_encode, QUERY_ENCODE_SET};
use actix_web::{client, Body, Binary, HttpMessage};
use actix_web::http::Method;
use std::collections::HashMap;
use futures::future::{Future, ok, err};
use actix_web::client::{ClientResponse, ClientConnector, Connect};

pub struct MyError<'a>(&'a str);

pub fn get<'a>(url: &'a str, params: &HashMap<&str, &str>) -> impl Future<Item=String, Error=MyError<'a>> {
    req(url, params, Method::GET)
}

fn req<'a>(url: &'a str, params: &HashMap<&str, &str>, method: Method) -> impl Future<Item=String, Error=MyError<'a>> {
    let mut combined_params = String::with_capacity(1024);
    for (key, value) in params {
        let value = percent_encode(value.as_bytes(), QUERY_ENCODE_SET).to_string();
        let _ = write!(&mut combined_params, "{}={}&", key, value);
    }

    let mut builder = client::ClientRequest::build();
    builder.uri(url);
    builder.timeout(Duration::new(6, 0));
    if method == Method::POST {
        builder.method(Method::POST);
    } else {
        builder.method(Method::GET);
    }
    let result = builder
        .header("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8")
        .body(combined_params);
    let c = match result {
        Ok(c) => c,
        Err(e) => {
            println!("request err1 = {}", e);
            return err(MyError("Error::InvalidUrlArgument"))
        },
    };
    c.send().then(|r| match r {
            Ok(cr) => {
                ok("".to_string())
            }
            Err(err) => {
                ok("".to_string())
            }
         })
}

Still got:

expected struct futures::FutureResult, found struct futures::Then
note: expected type futures::FutureResult<_, client2::MyError<'_>>
found type futures::Then<actix_web::client::SendRequest, futures::FutureResult<_, _>, [closure@src/client2.rs:51:15: 58:11]>

I checked Future chaining again, the argument in the lastest closure is Result, does that determined final returning type?


#5

return err(.. is type futures::FutureResult<...
Final statement is futures::Then<...
The type comes from the functions/expressions called. There isn’t a way to force the err to be of the same type as the main one unless coming out of the same code. (unlikely in all cases.)

Like @vitalyd mentions Either can wrap both in enum;

return Either::Left(err(...
Either::Right(c.send().then(...

#6

AHa, that worked!
Thank you so much.