Anatomy of a future


Back to index

Lifecycle

future-lifecycle2.png

Future trait

  • Output type is whatever the future produces
  • Pin is a memory safety type
  • Context is a generic way for the runtime to provide … context :)
pub trait Future {
    type Output;
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}

Writing a simple future

A simple future which will never complete

use core::{future::Future, pin::Pin, task::{Context, Poll}};

pub struct Never;

impl Future for Never {
    type Output = ();
    fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<()> {
        Poll::Pending
    }
}

Writing a simple future

A simple future which will immediately complete

use core::{future::Future, pin::Pin, task::{Context, Poll}};

pub struct Always;

impl Future for Always {
    type Output = ();
    fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<()> {
        Poll::Ready(())
    }
}

Await progress

  • In this example we're using async-std
  • You can run this example via cargo run --bin 01_never
use future_anatomy::never::*;

#[async_std::main]
async fn main() {
    let _ = MyFuture.await; // blocks forever
}

So what's up with Pin?

Pin (at a high level)

  • Pin is a way to instruct the Rust compiler to not allow moving of objects
  • Futures (internally) require self-referential structs
  • Self-referential structs are a bit problematic
  • Pin removes the ability for a type to be moved so that self-references remain valid

let sql: String = format!("SELECT FROM users WHERE usersname = {}", user);
let db_resp = self.query(&sql).await;

pin10.png

let sql: String = format!("SELECT FROM users WHERE usersname = {}", user);
let db_resp = self.query(&sql).await; // <-- creates a 'DbFuture' behind the scenes

pin11.png

let sql: String = format!("SELECT FROM users WHERE usersname = {}", user);
let db_resp = self.query(&sql).await;

pin12.png

Where does my future live?


  • Before being polled a future must be pinned to a particular place in memory
  • Try to be aware of where this is
  • Pin will create errors when trying to move the future

Want to learn more?

rustlatam2019.png

https://www.youtube.com/watch?v=skos4B5x7qE

A more useful future

  • Set internal boolean to true on first poll
  • Return Poll::Ready(()) on second poll
use core::{future::Future,pin::Pin,task::{Context, Poll}};

pub struct Twice(bool);
impl Twice {
    pub fn new() -> Self {
        Self(false)
    }
}

impl Future for Twice {
    type Output = ();
    fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<()> {
        if self.0 {
            dbg!(Poll::Ready(()))
        } else {
            self.0 = true;
            dbg!(Poll::Pending)
        }
    }
}

Not so useful…

  • The future only gets polled once!
❤ (tempest) ~/P/t/t/d/e/01-future-anatomy> cargo run --bin 02_twice
 Compiling 01-future-anatomy v0.1.0 (/home/Projects/talks/teaching-rust/dd-async/exercises/01-future-anatomy)
  Finished dev [unoptimized + debuginfo] target(s) in 0.69s
   Running `target/debug/02_twice`
 [src/twice.rs:17] Poll::Pending = Pending

future-lifecycle3.png

We need to wake our future again

use core::{future::Future,pin::Pin,task::{Context, Poll}};

pub struct Twice(bool);
impl Twice {
    pub fn new() -> Self {
        Self(false)
    }
}

impl Future for Twice {
    type Output = ();
    fn poll(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<()> {
        if self.0 {
            Poll::Ready(())
        } else {
            self.0 = true;
            ctx.waker().wake_by_ref();
            Poll::Pending
        }
    }
}

future-lifecycle3.png

 ❤ (tempest) ~/P/t/t/d/e/01-future-anatomy> cargo run --bin 03_twice
Finished dev [unoptimized + debuginfo] target(s) in 0.03s
 Running `target/debug/03_twice`
Future says: ()

In summary…

Desugaring

Async/ Await are relatively modern and hide what Futures look like under the hood

use async_std::{fs::File, io::Read};

async fn read_file(path: &str) -> std::io::Result<String> {

    let mut f = File::open(path).await?;
    let mut buf = String::new();
    f.read_to_string(&mut buf).await?;
    Ok(buf)

}

Desugaring

Async/ Await are relatively modern and hide what Futures look like under the hood

use async_std::{fs::File, io::Read};

fn read_file(path: &str) -> impl Future<Output=std::io::Result<String>> {
    async {
        let mut f = File::open(path).await?;
        let mut buf = String::new();
        f.read_to_string(&mut buf).await?;
        Ok(buf)
    }
}

Await the end of all things

  • Tell the executor to keep schedule a future
    • Just once though
    • Afterwards it needs to be woken up
    • .await gets desugared by the compiler

Back to index