DEV Community

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

Francesco Cogno on October 24, 2017

Intro If you are a programmer and you like Rust you might be aware of the future movement that is buzzing around the Rust community. G...
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).

Collapse
 
cerceris profile image
Andrei Borodaenko

Hi Francesco. Thanks for the tutorial!
Could you please explain why do you use my_fn_squared and not my_fut_squared in the code below?

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

Hi Andrei,
although there is no difference in the result between these two:

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

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

The signature is different:

futures::AndThen<impl futures::Future, std::result::Result<u32, std::boxed::Box<dyn std::error::Error>>, [closure@src/main.rs:25:45: 25:75]>

futures::AndThen<impl futures::Future, impl futures::Future, [closure@src/main.rs:30:45: 30:76]>

That said I think you are right, the code I used in the post is misleading. I will correct it right away!

Thank you very much,
Francesco

Collapse
 
vthg2themax profile image
Vince Pike

Given that futures are part of standard now, so you think learning tokio will still be a useful skill or should one spend their energy trying to figure out the new standard? (Which looks pretty complex)

Collapse
 
mindflavor profile image
Francesco Cogno

Most of the stuff above still applies to the "new" features (but there are some differences like the Pin type) so it can be useful to read it, especially because combinators are still allowed.

That said, the await syntax is the future so you will have to study it anyway. You might as well start from there and save some time.
It's still Rust though: what seems complex usually is! I plan to write about the new standard when the dust settles (and when I'm knowledgeable about it, which may never happen :))!

Collapse
 
thesirc profile image
King Claudy

Thank you for taking the time to write about futures! The post is really good and meets his expectations. May be you should consider writing a part of The Book on futures with the help of Alex Crichton

Collapse
 
mindflavor profile image
Francesco Cogno

Thank you for reading it! I will contact Alex Crichton and see if it makes sense to include it in the book.

The point is I would like these posts to be more relaxed in terms of both writing style and lexical accuracy: Rust tends to be intimidating as it is :). Take the official future tutorial for example: it's technically excellent but it starts with the why instead of how. An average programmer, such as me, might want to start with small steps instead of being thrown directly into the intricacies of the asynchronous programming.

Collapse
 
sreyassreelal profile image
__SyS__

Thank you so much.❤️