Understanding the new Generators

A new feature is coming to Rust in 2018—asynchronous generators. Generators are functions that can yield elements before returning from the function and resume executing later. This is great for the loops that we have seen in this chapter. With generators, we could directly replace many of the callbacks with the new async/await syntax.

This is still an unstable feature that can only be used in nightly, so it may be that the code you write becomes obsolete before stabilization. Let's see a simple example of a generator:

#![feature(generators, generator_trait)]

use std::ops::{Generator, GeneratorState};

fn main() {
let mut generator = || {
for i in 0..10 {
yield i;
}
return "Finished!";
};

loop {
match generator.resume() {
GeneratorState::Yielded(num) => println!("Yielded {}", num),
GeneratorState::Complete(text) => {
println!("{}", text);
break;
}
}
}
}

You will need to execute rustup override add nightly to run the example. If you run it, you will see this output:

The interesting thing here is that the generator function can perform any computation, and you can resume the computation once a partial result gets yielded, without needing buffers. You can test this by doing the following—instead of yielding something from the generator, just use it to print in the console. Let's see an example:

#![feature(generators, generator_trait)]

use std::ops::Generator;

fn main() {
let mut generator = || {
println!("Before yield");
yield;
println!("After yield");
};

println!("Starting generator...");
generator.resume();
println!("Generator started");
generator.resume();
println!("Generator finished");
}

If you run this example, you will see the following output:

As you can see, the function pauses its execution when it gets to a yield statement. If there is any data in that yield statement, the caller will be able to retrieve it. Once the generator is resumed, the rest of the function gets executed, until a yield or a return statement.

This, of course, is of great advantage for the futures we saw earlier. This is why the futures-await crate was created. This crate uses generators to make the implementation of asynchronous futures much easier. Let's rewrite the TCP echo server we created before using this crate. We will need to add the 0.2.0 version of the futures-await to the [dependencies] section of our Cargo.toml file and then start using a bunch of nightly features. Let's see some example code:

#![feature(proc_macro, conservative_impl_trait, generators)]

extern crate futures_await as futures;

use futures::prelude::*;
use futures::executor::block_on;

#[async]
fn retrieve_data_1() -> Result<i32, i32> {
Ok(1)
}

#[async]
fn retrieve_data_2() -> Result<i32, i32> {
Ok(2)
}

#[async_move]
fn add_data() -> Result<i32, i32> {
Ok(await!(retrieve_data_1())? + await!(retrieve_data_2())?)
}

fn main() {
println!("Result: {:?}", block_on(add_data()));
}

This example will have two asynchronous functions that could, for example, be retrieving information from the network. They get called by the add_data() function, which will wait for them to return before adding them up and returning a result. If you run it, you will see that the result is Ok(3). The line importing the futures_await crate as futures makes sense because the futures-await crate is just a small wrapper around the futures crate, and all the usual structures, functions, and traits are available.

The whole generators and async/await syntax is still being heavily worked on, but the Rust 2018 roadmap says it should be stabilized before the end of the year.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.22.77.63