Tokio alpha release with async & await

August 08, 2019

We're pleased to announce the release of the first Tokio alpha with async & await support. This includes updating all of the Tokio crates to use std::future instead of futures 0.1. It also includes adding async fn versions of the APIs.

Get it by adding:

tokio = "=0.2.0-alpha.1"

to your Cargo.toml file. This is how an echo server is now written:

#![feature(async_await)]

use tokio::net::TcpListener;
use tokio::prelude::*;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let addr = "127.0.0.1:8080".parse()?;
    let mut listener = TcpListener::bind(&addr)?;

    loop {
        let (mut socket, _) = listener.accept().await?;

        tokio::spawn(async move {
            let mut buf = [0; 1024];

            // In a loop, read data from the socket and write the data back.
            loop {
                let n = match socket.read(&mut buf).await {
                    // socket closed
                    Ok(n) if n == 0 => return,
                    Ok(n) => n,
                    Err(e) => {
                        println!("failed to read from socket; err = {:?}", e);
                        return;
                    }
                };

                // Write the data back
                if let Err(e) = socket.write_all(&buf[0..n]).await {
                    println!("failed to write to socket; err = {:?}", e);
                    return;
                }
            }
        });
    }
}

More examples are in the git repository.

The road so far

Tokio was first announced almost exactly three years ago. When Tokio was first released, it contained a minimal, single-threaded, scheduler and APIs for TCP. Since then, it has grown to include a multi-threaded work-stealing scheduler, APIs for timers, TCP, UDP, UDS, file system access, signal handling, managing child processes, high performance async aware channels, and more. All this thanks to a dedicated team of maintainers and an active community of contributors. Tokio would not be where it is without you! Thank you so much.

Tokio's goal has always been to provide an ergonomic library for writing asynchronous applications for Rust without compromising on performance. The past three years have been spent building out the Rust I/O ecosystem and discovering idioms and patterns for writing asynchronous Rust. Over the past three years, the Rust community built a vibrant ecosystem around Tokio.

Some libraries include:

(and of course, there are many more).

The number of users has been growing too! Users include:

Over the past three years, the Rust team has not been idle either. They have been working tirelessly on a huge feature that will greatly improve the experience of writing asynchronous code with Rust: async & await syntax. This new syntax is about to land on Rust stable.

All of Tokio's iteration to date was done while maintaining API stability. Tokio has not had a breaking release since the very first 0.1.0 release on September 9, 2016. Understandably, a long list of changes to make has been building up. The introduction of async & await syntax is the right moment to make these breaking changes.

What we have now

Today's release of Tokio 0.2.0-alpha.1 with async & await updates all of Tokio's components to use std::future, and all usage of futures 0.1 has been removed.

Changes include:

Using async fns for asynchronous operations.

When possible, async fn has been used to provide asynchronous operations. For example, acceping sockets from a TcpListener is done with the async accept function:

let (mut socket, _) = listener.accept().await?;

I/O operations are provided as async functions:

let n = socket.read(&mut buf).await?;

let n = socket.write(&buf).await?;

The same applies to most of the Tokio 0.2 APIs.

Starting the Tokio runtime

Setting the entry point of a Tokio application can now be done with a proc macro:

#[tokio::main]
async fn main() {
    println!("Hello from Tokio!");
}

Creating a runtime by hand is still supported. The proc macro just provides some sugar. The equivalent application would be:

fn main() {
    let mut rt = Runtime::new().unwrap();
    rt.block_on(async {
        println!("Hello from Tokio!");
    });
}

There is also a proc macro for tests that will set up a current thread runtime to run your tests:

#[tokio::test]
async fn my_test() {
    let addr = "127.0.0.1:8080".parse().unwrap();
    let mut listener = TcpListener::bind(&addr).unwrap();
    let addr = listener.local_addr().unwrap();

    // Connect to the listener
    TcpStream::connect(&addr).await.unwrap();
}

Nightly only

Because async & await are not available on the stable Rust channel yet, this alpha release requires using nightly Rust. Tokio includes a rust-toolchain file that tracks the nightly Tokio tests again.

Towards a final release

This alpha release represents the first step towards the next iteration of Tokio. Users are now able to experiment using Tokio and async & await syntax, but nothing is set in stone.

The availability of async & await syntax is the perfect time to issue a major revision of the full stack. All of Tokio's components require more changes, including ecosystem crates such as mio, bytes, and tracing. Focus will be on tight integration and performance, i.e. providing ergonomic high level APIs without sacrificing speed. Lessons learned over the past three years will be applied. Follow up blog posts will cover these changes in more detail as they happen.

As progress is made, alpha crates will be released. These releases will include breaking changes. Progress can be tracked on the issue tracker.

Given the scope of the changes, it will take a few months before a final release is ready. Estimating software release dates is hard, but let's aim for before the end of the year.

There is a ton of work to done on code, testing, and documentation. As always, your help is greatly appreciated! Check out the issue tracker or ping us on Gitter. We're always happy to mentor!