Future and if statements


#1

Hello,

I am trying to write a function returning a future which would have different behavior depending on some condition. A simplified version of my code is this:

pub fn get_token(&self) -> impl Future<Item=String, Error=failure::Error> {
    futures::future::lazy(|| {
        if self._token.is_none() || self._token.unwrap().updated.elapsed().as_secs() > TOKEN_VALIDITY_SECS {
            self.refresh_token()
        } else {
            futures::future::ok(self._token.unwrap().token.clone())
        }
    })
}

fn refresh_token(&self) -> impl Future<Item=String, Error=failure::Error> {
    futures::future::ok("my token".to_string())
}

Yet I receive the following error message from the compiler

error[E0308]: if and else have incompatible types
  --> src/hub/swgoh.rs:61:13
   |
61 | /             if self._token.is_none() || self._token.unwrap().updated.elapsed().as_secs() > TOKEN_VALIDITY_SECS {
62 | |                 self.refresh_token()
63 | |             } else {
64 | |                 futures::future::ok(self._token.unwrap().token.clone())
65 | |             }
   | |_____________^ expected anonymized type, found struct `futures::FutureResult`
   |
   = note: expected type `impl futures::Future`
              found type `futures::FutureResult<std::string::String, _>`

Yet according to the documentation impl<T, E> Future for FutureResult<T, E>. Is there a way to do something like this?

PS: to add more on the context, I want to hide the complexity of the validity of the token to the caller. The caller would just have to call the get_token() method to receive a token without knowing whether or not one need to do an auth call to refresh it


#2
fn refresh_token(&self) -> impl Future<Item=String, Error=failure::Error> {
    futures::future::ok("my token".to_string())
}

You should make the return type FutureResult<...>, rather than an anonymous type (i.e. impl Future<...>).

Alternatively, you can use future::Either inside get_token. But I’d just modify the return type of refresh_token since it’s a private method and so you’re not overexposing a concrete return type.


#3

actually the refresh_token() method will be a longer future chain (i.e. send a request, process it and return something) so I’ll have to type it with impl Future<...>

I’ll give a try to Either then, thanks for pointing it out @vitalyd


#4

the problem here is that impl Trait expects a single, specific type to be returned from the function. Even though your if and else branches both return types which implement Future, they are not the same type, and so impl Future<...> is not going to work here. You can get around this by returning a Box<dyn Future...> and boxing the return values.


#5

Did you have something like this in mind @vitalyd?

#[derive(Clone)]
pub(crate) struct Token {
    pub(crate) token: String,
    pub(crate) updated: Instant,
}

pub fn get_token(&self) -> impl Future<Item=String, Error=failure::Error> {
    if self._token.is_none() {
        Either::A(self.refresh_token())
    } else {
        let token = self._token.clone().unwrap();
        if token.updated.elapsed().as_secs() > TOKEN_VALIDITY_SECS {
            Either::A(self.refresh_token())
        } else {
            Either::B(futures::future::ok(token.token))
        }
    }
}

fn refresh_token(&self) -> impl Future<Item=String, Error=failure::Error> {
    futures::future::ok("my token".to_string())
}

If yes, out of curiousity how would you do if you have more than 2 different cases, i.e. when coupling this approach with a match statement?

@radix Boxing indeed works but I try to be cautious with boxes because of the performance costs (unless when I really have no choice). Thanks for the detailed explanation though. Even if I do understand it, I feel it beats the value of the generic impl keyword but well :slight_smile:


#6

You can nest Either indefinitely, each level adds one more variant. For example with 4 variants:

  • Either::A(…)
  • Either::B(Either::A(…))
  • Either::B(Either::B(Either::A(…)))
  • Either::B(Either::B(Either::B(…)))

#7

Thanks @jethrogb

A bit boilerplate but it works I guess :slight_smile:

That’s within the scope of one function/statement right?