Futures chaining try to return impl Future but failed

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?

1 Like

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=_>>

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

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?

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(...

AHa, that worked!
Thank you so much.