At the end of 2019 Rust officially supports async/await syntax, completing the last piece of the puzzle for Rust coroutines so that asynchronous code can be written in a concise manner similar to Go. However, it is important for programmers to understand how async/await works.

async

Simply put, async syntax generates an object that implements a Future. Async functions are as follows:

async fn foo(a) -> {... }Copy the code

The async keyword modifies the prototype of a function to return a Future trait object. The result of the execution is then returned wrapped in a new future, roughly equivalent to:

fn foo(a) -> impl Future<Output = ()> { async { ... }}Copy the code

More importantly, the async code block implements an anonymous Future trait object that wraps a Generator. This is a Generator that implements the Future. Await each time any Poll::Pending is returned in an async block the Generator yeild is called to give up execution and once execution resumes the Generator resume the rest of the process.

Here is the code for the state machine Future:

pub constfn from_generator<T>(gen: T) -> impl Future<Output = T::Return> where T: Generator<ResumeTy, Yield = ()>, { struct GenFuture<T: Generator<ResumeTy, Yield = ()>>(T); impl<T: Generator<ResumeTy, Yield = ()>> ! Unpinfor GenFuture<T> {}
    
    impl<T: Generator<ResumeTy, Yield = ()>> Future for GenFuture<T> {
        type Output = T::Return;
        fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll
        <:output>
          { let gen = unsafe { Pin::map_unchecked_mut(self, |s| &mut s.0) }; match gen.resume(ResumeTy(NonNull::from(cx).cast::
        static> > ())) {
                GeneratorState::Yielded(()) => Poll::Pending,  // When the code cannot continue, surrender control, return Pending, and wait for awakening
                GeneratorState::Complete(x) => Poll::Ready(x), // The execution is complete}}}GenFuture(gen)
}
Copy the code

You can see that this particular Future is run through the Generator. Each time Gen.resume () executes the code in the Async block sequentially until yield is encountered. An. Await statement in an async block that cannot complete immediately calls yield to surrender control until the next resume. When the state machine completes, async block returns Poll::Ready, indicating that the Future is done.

await

Each await itself is like an executor that queries the state of the Future in a loop. If Pending is returned, yield; otherwise, the loop exits, terminating the current Future.

The code logic is roughly as follows:

loop {
	match some_future.poll() {
		Pending => yield,
		Ready(x) => break}}Copy the code

To understand how async/await works, let’s look at a simple example:

async fn foo(a) {
	do_something_1(a); some_future.await;do_something_2(a); }Copy the code

Foo, an asynchronous function decorated with async, is overwritten as a Future driven by a Generator state machine, with a some_future.await inside, interspersed with other operations such as do_SOMETHING_x (). When foo().await is executed, do_something_1() is first done, and some_future.await is executed. If some_future returns Pending, the Pending is converted to yield, If some_future returns Ready, some_future.await is finished. If some_future returns Ready, some_future.await is finished. Then foo() starts executing do_something_2().

The key point here is that because of state machine control, when foo() is awakened again, it does not repeat do_SOMETHING_1 (), but instead continues some_future.await from where it was last yielded, which is equivalent to a task switch. This is also how stackless coroutines work.

conclusion

Async /await uses a state machine to control the flow of code and work with executors to switch coroutines. After that, writing asynchronous code does not require manual writing of the Future and its poll methods. In particular, the state machine of asynchronous logic is automatically generated by Async, greatly simplifying the programmer’s work. Although async/await has not been around for a long time and currently only async/await code is not mainstream, it is optimistic that more projects will use this new syntax in the future.

Futures Explained in 200 Lines of Rust

Author: Xie Jingwei, known as “Brother Dao”, 20 years OF IT veteran, data communication network expert, telecom network architect, currently serves as the development director of Netwarps. Brother Dao has years of practical experience in operating system, network programming, high concurrency, high throughput, high availability and other fields, and has a strong interest in new technologies in network and programming.

Shenzhen Netwarps Technology Co., LTD. (Netwarps), focusing on the research and development and application of Internet secure storage technology, is an advanced secure storage infrastructure provider, the main products are decentralized file system (DFS), enterprise Alliance chain platform (EAC), block chain operating system (BOS).

Wechat official account: Netwarps