Run Await With Me

Asynchronous Programming at RustConf 2018
Before we get started

$ rustup update nightly
$ rustup override set nightly
                    
What this class is about

  • Internals of Futures/Async/Await
  • Not about Futures-rs/Tokio
  • Understanding what Tokio/Futures-rs does for you
Lots to cover!

  • Async Rust history
  • Structure of futures
  • Implement futures executor
  • async/await and pinning
Introduction
  • Michael Gattozzi
  • 3+ Years of Rust
  • wasm-wg member
  • Maker of groan inducing puns
  • Anime nerd
  • Handle for everything: mgattozzi
  • @ me so I can get free dopamine
Introduction
  • Taylor Cramer
  • @cramertj on everything
  • Oxidizing since early 2016
  • Lang Design and Compiler Team Member
  • WG Net Async Lead
  • Googler working on Fuchsia
A few quick rules
Ask about missed references during breaks,
and don't hate on my puns :D
It's okay to not know things
Questions are good and encouraged!
Have fun! We're learning exciting new things together!

The Past and Present of Futures


Rust used to have lightweight userspace threads,
often called M:N threads or "green threads."
libgreen was introduced back in 2013!
Stack composed of runtime linked list of stacks:
  • Caused "stack thrashing"
  • FFI expects large stacks
libgreen was removed in 2014!
  • No more runtime system
  • No more garbage collection
Is ergonomic async code possible in Rust?
Spoiler Alert: Yes

Back to the Future


So what's a Future?
Future = asynchronous computation

  • Network IO
  • Message from another thread or computer
  • Chained operations
In other languages:

  • Future is a concrete type
  • Actively driven to completion by runtime
  • Methods create callbacks spawned onto an executor
  • Allocation heavy (one per callback/map/and_then)
In Rust:

  • Future is a trait
  • Does nothing unless passed to an executor and run
  • Methods create wrapper types which run their children
  • One allocation per top-level task
How are Rust futures run?

  • Future spawned onto executor
  • Executor polls a Future
  • Future says "I'm not ready yet!"
  • Executor goes to sleep until Future sends a wakeup

Poll Me Maybe

How Futures act if you don't poll them for values
What is Poll?
Simplified signature:

enum Poll<T> {
    Ready(T),
    Pending
}

trait Future {
    type Output;
    fn poll(&mut self, wake: fn())
        → Poll<Self::Output>;
}
                    
The real thing:

enum Poll<T> {
    Ready(T),
    Pending
}

trait Future {
    type Output;
    fn poll(self: PinMut<Self>, cx: &mut task::Context)
        → Poll<Self::Output>;
}
                    

Concrete example time!

Let's make the simplest possible future, `ready`.

fn ready(x: i32) → impl Future<Output = i32> {
    ...
}
                    
  • Takes `i32` as input
  • Returns an immediately-ready Future

struct Ready { value: i32 }

fn ready(value: i32) → Ready {
    Ready { value }
}

impl Future for Ready {
    type Output = i32;
    fn poll(self: PinMut, cx: &mut task::Context)
        → Poll<Self::Output>
    {
        Poll::Ready(self.value)
    }
}

                    
Most futures aren't immediately ready.
  • Return Poll::Pending
  • Schedule to receive wakeup
  • Store "Waker" type (callback fn)
The Task Context and the Waker Type

struct Context { ... }
impl Context {
    /// Returns the waker for the current task
    fn waker(&self) → &Waker { ... }
}

struct Waker { ... }
impl Waker {
    /// Wakes up the task associated with this `Waker`.
    fn wake(&self) { ... }
}
                  
Let's make a Future that needs to be awoken!
Once.
Immediately.

struct AlmostReady { ready: bool, value: i32 }

fn almost_ready(value: i32) → AlmostReady {
    AlmostReady { ready: false, value }
}
                    

struct AlmostReady { ready: bool, value: i32 }
impl Future for AlmostReady {
    type Output = i32;
    fn poll(mut self: PinMut<Self>, cx: &mut task::Context)
        → Poll<Self::Output>
    {
        if self.ready {
            Poll::Ready(self.value)
        } else {
            self.ready = true;
            cx.waker().wake();
            Poll::Pending
        }
    }
}               
Okay now it's your turn to implement a Future!
Don't worry you already have most of what you need!
Build a Future
  • Create a future that will resolve after 2 seconds
  • Don't block the event loop-- no sleeping on the main thread
  • To get started:
    
    $ git clone https://github.com/mgattozzi/async-await-class.git
    $ cd async-await-class
    $ vim src/timer.rs # or another editor of your choice
    $ cargo run --bin timer # to check your solution
                            
  • Replace the FIXMEs with proper solutions!
  • When done, you'll see the message "Completed future after 2s"
  • Ask lots of questions! The more you ask, the more we all learn!

Break Time

Y'all have worked hard!

Building a Futures Executor

TinyExecutor
Don't worry it won't be like this
Open up the code!

$ emacs src/executor.rs # or your favorite choice of editor
                    
The primary building blocks here are:

  • The Executor and Spawner types (channel ends)
  • The Task type (a pair of Mutex and Spawner)
Let's a look at the implementation of the `Spawn` trait together.
Your Task should you choose to Execute it
  • Fill out the two FIXMEs to get a working executor!
  • 
    $ cargo run --bin executor # to check your solution
                          
  • We recommend doing `impl Wake for Task` first as it is easier
  • Create the actual Executor that will take things off the queue and poll them
  • The Executor should run as long as it receives tasks to execute
  • Ask questions if stuck! We'll be happy to help you out, however we can.

Break 2

Electric Breakalloo

async/await!()

What is Rust's async/await feature?

  • Turns straight-line code into Future types
  • Similar to how closures generate Fn types
Chain Two Operations: Manual Future

fn download_and_write_tweets(
    user: String,
    socket: Socket,
) → impl Future<Output = io::Result<()>> {
    TwoOps::First {
        fut: pull_down_tweets(user),
        socket_for_later: socket,
    }
}

enum TwoOps {
    First {
        fut: PullDownTweets
        socket_for_later: Socket,
    },
    Second(WriteTweetsToSocket),
    Taken,
}

impl Future for TwoOps {
    type Output = io::Result<()>;
    fn poll(mut self: PinMut<Self>, cx: &mut task::Context)
        &rarrow; Poll<Self::Output>
    {
        loop {
            match mem::replace(&mut *self, TwoOps::Taken) {
                TwoOps::First { fut, socket_for_later } => {
                    match fut.poll_unpin(cx)? {
                        Poll::Ready(tweets) => {
                             *self = TwoOps::Second(write_tweets(tweets, socket_for_later));
                        }
                        Poll::Pending => {
                            *self = TwoOps::First { fut, socket_for_later };
                            return Poll::Pending;
                        }
                    }
                }
                TwoOps::Second(fut) => {
                    match fut.poll_unpin(cx) {
                        Poll::Ready(res) => {
                            return Poll::Ready(res);
                        }
                        Poll::Pending => {
                            *self = TwoOps::Second(fut);
                            return Poll:Pending;
                        }
                    }
                }
                TwoOps::Taken => panic!("oh noes"),
            }
        }
    }
}
                    
Chain Two Operations: Combinators

fn download_and_write_tweets(
    user: String,
    socket: Socket,
) → impl Future<Output = io::Result<()>> {
    pull_down_tweets(user)
        .and_then(move |tweets| write_tweets(socket))
}
                    
Chain Two Operations: async/await

async fn download_and_write_tweets(
    user: &str,
    socket: &Socket,
) → io::Result<()> {
    let tweets = await!(pull_down_tweets(user))?;
    await!(write_tweets(socket))
}
                    

Pinning



A Quick Overview
What type does this turn into?

async fn do_with_str() {
    let x = "foo".to_string();
    let x_ref: &str = &x;
    await!(thing_with_string(x_ref));
}
                    
Maybe something like this?

struct DoWith {
    x: String,
    // this holds a pointer to the field above!
    thing_with_string_fut: Thing<'magic>;
}
                    
When a future is polled, it may become self-referential,
so they must be "pinned".
Once a type has been pinned, it isn't allowed to move.
Some futures don't need to be kept in one place, so they
can be "unpinned" (via the `Unpin` trait).
Questions on pinning?
We're a couple hours into the Future
We've learned a lot!

  • Async Rust history
  • Structure of Rust futures
  • Implemented futures (ready, almost_ready, timer)
  • Made an executor
  • async/await and pinning
If I polled you all on how confident you are with Futures
I think I would get a response of Poll::Ready(Knowledge)
Some cool things to look out for down the line
Futures-rs is an abstraction over Futures

Nice iterator like adaptors for Futures
Tokio is a solid more efficient event loop for you to use!

No need for you to make an event loop
What does this all mean?
You'll have an understanding of what your code is doing under the hood
You'll appreciate those nice libraries and abstractions
They won't be black magic now though
Unfortunately this is the end of the class
It's time to go
Big thanks to cramertj and aturon for helping with this class through planning, code, and more
Thank you all for coming!

Questions?