DEV Community

Discussion on: Rust futures: an uneducated, short and hopefully not boring tutorial - Part 1

Collapse
 
jnordwick profile image
Jason Nordwick • Edited

Why do you need the lambda?

let chained_future = my_fut().and_then(|retval| my_fn_squared(retval));

why not?

let chained_future = my_fut().and_then(my_fn_squared);
Collapse
 
mindflavor profile image
Francesco Cogno • Edited

You do not need the lambda in this case, your code will work just fine. I used the lambda for two reasons:

  1. Blog post logic
  2. Global ergonomics (in other words, taste :))

Blog post logic

In the post I was migrating step-by-step from a non-async code to an async one. In the previous step I had this code:

let mut reactor = Core::new().unwrap();

let retval = reactor.run(my_fut()).unwrap();
println!("{:?}", retval);

let retval2 = reactor.run(my_fut_squared(retval)).unwrap();
println!("{:?}", retval2);

The logical following step was to chain those statements into one, so I kept the same binding names (retval and retval2):

let chained_future = my_fut().and_then(|retval| my_fn_squared(retval));
let retval2 = reactor.run(chained_future).unwrap();
println!("{:?}", retval2);

IMHO hiding the retval value would have been harder to follow.

Taste

I generally prefer to use closures as they are more explicit. Let me draft an example.
If we take into account these three Future returning functions:

fn my_fut() -> impl Future<Item = u32, Error = Box<Error>> {
    ok(100)
}

fn my_fut_squared(i: u32) -> impl Future<Item = u32, Error = Box<Error>> {
    ok(i * i)
}

fn my_fut_two(i: u32, j: u32) -> impl Future<Item = u32, Error = Box<Error>> {
    ok(i + j)
}

The first one takes no parameters, the second one just one and the third one two.

You can chain the first two as you did. That's the functional approach:

let chained_future_simple = my_fut().and_then(my_fn_squared);
let retval2 = reactor.run(chained_future_simple).unwrap();
println!("chained_future_simple == {:?}", retval2);

That is, the parameter is implicitly passed between the futures. But what about chaining the first and the third future? This will not work because the third future expects two parameters:

let chained_future_two = my_fut().and_then(my_fut_two);
let retval2 = reactor.run(chained_future_two).unwrap();
println!("chained_future_two == {:?}", retval2);

It will give, unsurprisingly:

error[E0593]: function is expected to take 1 argument, but it takes 2 arguments
   --> src/main.rs:141:27
    |
141 |     let retval2 = reactor.run(chained_future_two).unwrap();
    |                           ^^^ expected function that takes 1 argument
    |
    = note: required because of the requirements on the impl of `futures::Future` for `futures::AndThen<impl futures::Future, _, fn(u32, u32) -> impl futures::Future {my_fut_two}>`

You cannot curry the function either:

let chained_future_two = my_fut().and_then(my_fut_two(30));
let retval2 = reactor.run(chained_future_two).unwrap();
println!("chained_future_two == {:?}", retval2);

As Rust will complain like this:

error[E0061]: this function takes 2 parameters but 1 parameter was supplied
   --> src/main.rs:140:59
    |
31  | / fn my_fut_two(i: u32, j: u32) -> impl Future<Item = u32, Error = Box<Error>> {
32  | |     ok(i + j)
33  | | }
    | |_- defined here
...
140 |       let chained_future_two = my_fut().and_then(my_fut_two(30));
    |                                                             ^^ expected 2 parameters

For this reason I prefer the closures: the above code can be expressed like this:

let chained_future_two = my_fut().and_then(|retval| my_fut_two(retval, 30));
let retval2 = reactor.run(chained_future_two).unwrap();
println!("chained_future_two == {:?}", retval2);

It's more verbose but in this case I like the explicitness more. Also Rust gives you control if you want to move the captured variables so you have control over that too.
In the end I think it's a matter of taste (I don't know if there is speed difference so maybe one solution is better than the other in that regard).