3

The function below, taken from here:

fn connection_for(
    &self,
    pool_key: PoolKey,
) -> impl Future<Output = Result<Pooled<PoolClient<B>>, ClientError<B>>> {

    let checkout = self.pool.checkout(pool_key.clone());
    let connect = self.connect_to(pool_key);

    let executor = self.conn_builder.exec.clone();
    // The order of the `select` is depended on below...
    future::select(checkout, connect).then(move |either| match either {
         ...

should return a Future. However, it returns the return result of

    future::select(checkout, connect).then(...)

Where .then does this:

fn then<Fut, F>(self, f: F) -> Then<Self, Fut, F>

Which is not a Future. How it this possible?

I'm trying to understand what is returned by this function. This is the end of the '.then':

       Either::Right((Err(err), checkout)) => Either::Right(Either::Right({
            if err.is_canceled() {
                Either::Left(checkout.map_err(ClientError::Normal))
            } else {
                Either::Right(future::err(ClientError::Normal(err)))
            }
        })),

It looks like it returns Either::Right(Either::Right of something. I'm confused.

kmdreko
  • 42,554
  • 6
  • 57
  • 106
Gatonito
  • 1,662
  • 5
  • 26
  • 55
  • I'm confused, [`Then` *is* a `Future`](https://docs.rs/futures/0.3.12/futures/future/struct.Then.html#impl-Future) – kmdreko Feb 18 '21 at 06:33
  • @kmdreko ok, so `.then(f: F)` accepts `f: ()->Future`. Does this make ` Either::Right(Either::Right({...` be converted to a future by type inference? – Gatonito Feb 18 '21 at 06:46
  • [`Either` is a `Future` if its `A` and `B` are](https://docs.rs/futures/0.3.12/futures/future/enum.Either.html#impl-Future). It looks like they're using `Either`s to combine all the futures that may be run (looks like 6 in total) into a single type. – kmdreko Feb 18 '21 at 06:51
  • @kmdreko ok, so `Either` is indeed a `Future`, but how could it be a future of `Result>, ClientError>`? Which is the type needed for this `Future`. The outermost `Either` is a future, but the insides are again `Either`s – Gatonito Feb 18 '21 at 07:02
  • @kmdreko I mean, why it returns `Either::Right(Either::Right(Either::left(error)))` instead of returning `Future(error)`? – Gatonito Feb 18 '21 at 07:25

1 Answers1

3

.then() is used for chaining two futures together. It returns a Then<Fut1, Fut2, F>, which is a Future that does the work of: polling the first future, processing the result with given function, and polling the resulting future.


The Either type is designed to combine two different futures having the same associated output into a single type. Say you were making a function that will return two different futures depending on the input:

async fn do_a() -> () {}
async fn do_b() -> () {}

fn do_something(cond: bool) -> impl Future<Output = ()> {
    if cond {
        do_a()
    }
    else {
        do_b()
    }
}

This won't compile because the Futures returned by do_a() and do_b() are different types. The Rust compiler is kind enough to suggest using Box<dyn Future> to return different types dynamically, however the extra indirection is sometimes not desired for performance reasons and is not necessary.

The above can be implemented to return a concrete Future using Either.

use std::future::Future;
use futures::future::Either;

async fn do_a() -> () {}
async fn do_b() -> () {}

fn do_something(cond: bool) -> impl Future<Output = ()> {
    if cond {
        Either::Left(do_a())
    }
    else {
        Either::Right(do_b())
    }
}

The linked code in question looks a bit gnarly because it has to not only reconcile two different futures, but five. But you can nest Eithers without a problem, since they themselves are Futures. Its more like this:

use std::future::Future;
use futures::future::Either;

async fn do_a() -> () {}
async fn do_b() -> () {}
async fn do_c() -> () {}
async fn do_d() -> () {}
async fn do_e() -> () {}

fn do_something(cond1: bool, cond2: bool, cond3: bool) -> impl Future<Output = ()> {
    if cond1 {
        Either::Left(do_a())
    }
    else if cond2 {
        if cond3 {
            Either::Right(Either::Left(Either::Left(do_b())))
        }
        else {
            Either::Right(Either::Left(Either::Right(do_c())))
        }
    }
    else {
        if cond3 {
            Either::Right(Either::Right(Either::Left(do_d())))
        }
        else {
            Either::Right(Either::Right(Either::Right(do_e())))
        }
    }
}

It all ends up creating a impl Future<Output = Result<Pooled<PoolClient<B>>, ClientError<B>>> because all the individual futures it can potentially yield each return a Result<Pooled<PoolClient<B>>, ClientError<B>>.

kmdreko
  • 42,554
  • 6
  • 57
  • 106
  • Why do_a() and do_b() return different things? It seems equal. Also, if I'm just intersted in the result of `do_x`, why nest it in lots of `Either::Left`, `Either::Right` if I can just do one like you did on `cond1`? – Gatonito Feb 18 '21 at 08:35
  • No two `async` functions return the same type, they are each unique. This is the same as closures, its just part of how they're designed. You don't want arbitrary changes potentially making two types identical or not. An `Either` can only have two types, so to get more you have to nest them. Think of the `Left`s and `Right`s as building a tree so that each unique `Left`/`Right` path leads to a different type. – kmdreko Feb 18 '21 at 08:53