Ch 9 — Coroutines, Self-Referential Structs, and Pinning

Asynchronous Programming in Rust — Carl Fredrik Samson · pages 234–271 · 282 text blocks · 5 figures

Coroutines, Self-Referential Structs, and Pinning In this chapter, we’ll start by improving our coroutines by adding the ability to store variables across state changes. We’ll see how this leads to our coroutines needing to take references to themselves and the issues that arise as a result of that. The reason for dedicating a whole chapter to this topic is that it’s an integral part of getting async/await to work in Rust, and also a topic that is somewhat difficult to get a good understanding of. The reason for this is that the whole concept of pinning is foreign to many developers and just like the Rust ownership system, it takes some time to get a good and working mental model of it. Fortunately, the concept of pinning is not that difficult to understand, but how it’s implemented in the language and how it interacts with Rust’s type system is abstract and hard to grasp. While we won’t cover absolutely everything about pinning in this chapter, we’ll try to get a good and sound understanding of it. The major goal here is to feel confident with the topic and understand why we need it and how to use it. As mentioned previously, this chapter is not only about pinning in Rust, so the first thing we’ll do is make some important improvements where we left off by improving the final example in Chapter 8. Then, we’ll explain what self-referential structs are and how they’re connected to futures before we explain how pinning can solve our problems. This chapter will cover the following main topics

• Improving our example 1 – variables • Improving our example 2 – references • Improving our example 3 – this is… not… good… • Discovering self-referential structs

214 Coroutines, Self-Referential Structs, and Pinning

         • Pinning in Rust
         • Improving our example 4 – pinning to the rescue
      Technical requirements
      The examples in this chapter will build on the code from the previous chapter, so the requirements are
      the same. The examples will all be cross-platform and work on all platforms that Rust (https://
      doc.rust-lang.org/stable/rustc/platform-support.html) and mio (https://
      github.com/tokio-rs/mio#platforms) support. The only thing you need is Rust installed
      and this book’s GitHub repository downloaded locally. All the code in this chapter can be found in
      the ch09 folder.
      To follow the examples step by step, you’ll also need corofy installed on your machine. If you didn’t
      install it in Chapter 7, install it now by going into the ch07/corofy folder in the repository and
      running the following:
        cargo install --force --path .
      We’ll also use delayserver in this example, so you need to open a separate terminal, enter the
      delayserver folder at the root of the repository, and write cargo run so that it’s ready and
      available for the examples going forward.
      Remember to change the port number in the code if you have to change what port delayserver
      listens on.
      Improving our example 1 – variables
      So, let’s recap what we have at this point by continuing where we left off in the previous chapter. We
      have the following:
         • A Future trait
         • A coroutine implementation using coroutine/await syntax and a preprocessor
         • A reactor based on mio::Poll
         • An executor that allows us to spawn as many top-level tasks as we want and schedules the ones
           that are ready to run
         • An HTTP client that only makes HTTP GET requests to our local delayserver instance
      It’s not that bad – we might argue that our HTTP client is a little bit limited, but that’s not the focus of
      this book, so we can live with that. Our coroutine implementation, however, is severely limited. Let’s
      take a look at how we can make our coroutines slightly more useful.
      The biggest downside with our current implementation is that nothing – and I mean nothing – can
      live across wait points. It makes sense to tackle this problem first.
                                                                    Improving our example 1 – variables   215

Let’s start by setting up our example. We’ll use the “library” code from d-multiple-threads example in Chapter 8 (our last version of the example), but we’ll change the main.rs file by adding a shorter and simpler example. Let’s set up the base example that we’ll iterate on and improve in this chapter.

Setting up the base example

Note You can find this example in this book’s GitHub repository under ch09/a-coroutinesvariables.

Perform the following steps:

1. Create a folder called a-coroutines-variables. 2. Enter the folder and run cargo init. 3. Delete the default main.rs file and copy everything from the ch08/d-multiplethreads/src folder into the ch10/a-coroutines-variables/src folder. 4. Open Cargo.toml and add the dependency on mio to the dependencies section: mio = {version = "0.8", features = ["net", "os-poll"]}

You should now have a folder structure that looks like this:

  src
    |-- runtime
         |-- executor.rs
         |-- reactor.rs
    |-- future.rs
    |-- http.rs
    |-- main.rs
    |-- runtime.rs

We’ll use corofy one last time to generate our boilerplate state machine for us. Copy the following into main.rs:

ch09/a-coroutines-variables/src/main.rs mod future; mod http; mod runtime; use crate::http::Http;

216 Coroutines, Self-Referential Structs, and Pinning

        use future::{Future, PollState};
        use runtime::Waker;
        fn main() {
            let mut executor = runtime::init();
            executor.block_on(async_main());
        coroutine fn async_main() {
            println!("Program starting");
            let txt = Http::get("/600/HelloAsyncAwait").wait;
            println!("{txt}");
            let txt = Http::get("/400/HelloAsyncAwait").wait;
            println!("{txt}");
      This time, let’s take a shortcut and write our corofied file directly back to main.rs since we’ve compared
      the files side by side enough times at this point. Assuming you’re in the base folder, a-coroutine-
      variables, write the following:
        corofy ./src/main.rs ./src/main.rs
      The last step is to fix the fact that corofy doesn’t know about Waker. You can let the compiler guide
      you to where you need to make changes by writing cargo check, but to help you along the way,
      there are three minor changes to make (note that the line number is the one reported by re-writing
      the same code that we wrote previously):
        64: fn poll(&mut self, waker: &Waker)
        82: match f1.poll(waker)
        102: match f2.poll(waker)
      Now, check that everything is working as expected by writing cargo run.
      You should see the following output (the output has been abbreviated to save a little bit of space):
        Program starting
        FIRST POLL - START OPERATION
        main: 1 pending tasks. Sleep until notified.
        HTTP/1.1 200 OK
        [==== ABBREVIATED ====]
        HelloAsyncAwait
        main: All tasks are finished
                                                                Improving our example 1 – variables   217

Note Remember that we need delayserver running in a terminal window so that we get a response to our HTTP GET requests. See the Technical requirements section for more information.

Now that we’ve got the boilerplate out of the way, it’s time to start making the improvements we talked about.

Improving our base example We want to see how we can improve our state machine so that it allows us to hold variables across wait points. To do that, we need to store them somewhere and restore the variables that are needed when we enter each state in our state machine.

Tip Pretend that these rewrites are done by corofy (or the compiler). Even though corofy can’t do these rewrites, it’s possible to automate this process as well.

Or coroutine/wait program looks like this:

  coroutine fn async_main() {
      println!("Program starting");
      let txt = Http::get("/600/HelloAsyncAwait").wait;
      println!("{txt}");
      let txt = Http::get("/400/HelloAsyncAwait").wait;
      println!("{txt}");

We want to change it so that it looks like this:

  coroutine fn async_main() {
      let mut counter = 0;
      println!("Program starting");
      let txt = http::Http::get("/600/HelloAsyncAwait").wait;
      println!("{txt}");
      counter += 1;
      let txt = http::Http::get("/400/HelloAsyncAwait").wait;
      println!("{txt}");
      counter += 1;
      println!("Received {} responses.", counter);

218 Coroutines, Self-Referential Structs, and Pinning

      In this version, we simply create a counter variable at the top of our async_main function and
      increase the counter for each response we receive from the server. At the end, we print out how many
      responses we received.
         Note
         For brevity, I won’t present the entire code base going forward; instead, I will only present the
         relevant additions and changes. Remember that you can always refer to the same example in
         this book’s GitHub repository.
      The way we implement this is to add a new field called stack to our Coroutine0 struct:
      ch09/a-coroutines-variables/src/main.rs
        struct Coroutine0 {
            stack: Stack0,
            state: State0,
      The stack fields hold a Stack0 struct that we also need to define:
      ch09/a-coroutines-variables/src/main.rs
        #[derive(Default)]
        struct Stack0 {
            counter: Option<usize>,
      This struct will only hold one field since we only have one variable. The field will be of the
      Option<usize> type. We also derive the Default trait for this struct so that we can initialize
      it easily.
                                                                     Improving our example 1 – variables   219

Note Futures created by async/await in Rust store this data in a slightly more efficient manner. In our example, we store every variable in a separate struct since I think it’s easier to reason about, but it also means that the more variables we need to store, the more space our coroutine will need. It will grow linearly with the number of different variables that need to be stored/restored between state changes. This could be a lot of data. For example, if we have 100 state changes that each need one distinct i64-sized variable to be stored to the next state, that would require a struct that takes up 100 * 8b = 800 bytes in memory. Rust optimizes this by implementing coroutines as enums, where each state only holds the data it needs to restore in the next state. This way, the size of a coroutine is not dependent on the total number of variables; it’s only dependent on the size of the largest state that needs to be saved/restored. In the preceding example, the size would be reduced to 8 bytes since the largest space any single state change needed is enough to hold one i64-sized variable. The same space will be reused over and over. The fact that this design allows for this optimization is significant and it’s an advantage that stackless coroutines have over stackful coroutines when it comes to memory efficiency.

The next thing we need to change is the new method on Coroutine0:

ch09/a-coroutines-variables/src/main.rs
  impl Coroutine0 {
      fn new() -> Self {
          Self {
              state: State0::Start,
              stack: Stack0::default(),

The default value for stack is not relevant to us since we’ll overwrite it anyway. The next few steps are the ones of most interest to us. In the Future implementation for Coroutine0, we’ll pretend that corofy added the following code to initialize, store, and restore the stack variables for us. Let’s take a look at what happens on the first call to poll now:

ch09/a-coroutines-variables/src/main.rs
  State0::Start => {
                      // initialize stack (hoist variables)
                      self.stack.counter = Some(0);
                      // ---- Code you actually wrote ----
                      println!("Program starting");

220 Coroutines, Self-Referential Structs, and Pinning

                            // ---------------------------------
                            let fut1 = Box::new( http::Http::get("/600/
        HelloAsyncAwait"));
                            self.state = State0::Wait1(fut1);
                            // save stack
      Okay, so there are some important changes here that I’ve highlighted. Let’s go through them:
         • The first thing we do when we’re in the Start state is add a segment at the top where we
           initialize our stack. One of the things we do is hoist all variable declarations for the relevant
           code section (in this case, before the first wait point) to the top of the function.
         • In our example, we also initialize the variables to their initial value, which in this case is 0.
         • We also added a comment stating that we should save the stack, but since all that happens before
           the first wait point is the initialization of counter, there is nothing to store here.
      Let’s take a look at what happens after the first wait point:
      ch09/a-coroutines-variables/src/main.rs
        State0::Wait1(ref mut f1) => {
                            match f1.poll(waker) {
                                PollState::Ready(txt) => {
                                    // Restore stack
                                    let mut counter = self.stack.counter.
        take().unwrap();
                                    // ---- Code you actually wrote ----
                                    println!("{txt}");
                                    counter += 1;
                                    // ---------------------------------
                                    let fut2 = Box::new(
        http::Http::get("/400/HelloAsyncAwait"));
                                    self.state = State0::Wait2(fut2);
                                    // save stack
                                    self.stack.counter = Some(counter);
                                PollState::NotReady => break
        PollState::NotReady,
                                                                     Improving our example 1 – variables     221

Hmm, this is interesting. I’ve highlighted the changes we need to make. The first thing we do is to restore the stack by taking ownership over the counter (take()replaces the value currently stored in self.stack.counter with None in this case) and writing it to a variable with the same name that we used in the code segment (counter). Taking ownership and placing the value back in later is not an issue in this case and it mimics the code we wrote in our coroutine/wait example. The next change is simply the segment that takes all the code after the first wait point and pastes it in. In this case, the only change is that the counter variable is increased by 1. Lastly, we save the stack state back so that we hold onto its updated state between the wait points.

Note In Chapter 5, we saw how we needed to store/restore the register state in our fibers. Since Chapter 5 showed an example of a stackful coroutine implementation, we didn’t have to care about stack state at all since all the needed state was stored in the stacks we created. Since our coroutines are stackless, we don’t store the entire call stack for each coroutine, but we do need to store/restore the parts of the stack that will be used across wait points. Stackless coroutines still need to save some information from the stack, as we’ve done here.

When we enter the State0::Wait2 state, we start the same way:

ch09/a-coroutines-variables/src/main.rs
  State0::Wait2(ref mut f2) => {
                      match f2.poll(waker) {
                          PollState::Ready(txt) => {
                              // Restore stack
                              let mut counter = self.stack.counter.
  take().unwrap();
                              // ---- Code you actually wrote ----
                              println!("{txt}");
                              counter += 1;
                              println!(«Received {} responses.»,
  counter);
                              // ---------------------------------
                              self.state = State0::Resolved;
                              // Save stack (all variables set to None
  already)
                              break PollState::Ready(String::new());

222 Coroutines, Self-Referential Structs, and Pinning

                                PollState::NotReady => break
        PollState::NotReady,
      Since there are no more wait points in our program, the rest of the code goes into this segment and
      since we’re done with counter at this point, we can simply drop it by letting it go out of scope. If
      our variable held onto any resources, they would be released here as well.
      With that, we’ve given our coroutines the power of saving variables across wait points. Let’s try to run
      it by writing cargo run.
      You should see the following output (I’ve removed the parts of the output that remain unchanged):
        …
        HelloAsyncAwait
        Received 2 responses.
        main: All tasks are finished
      Okay, so our program works and does what’s expected. Great!
      Now, let’s take a look at an example that needs to store references across wait points since that’s an
      important aspect of having our coroutine/wait functions behave like “normal” functions.
      Improving our example 2 – references
      Let’s set everything up for our next version of this example:
         • Create a new folder called b-coroutines-references and copy everything from
           a-coroutines-variables over to it
         • You can change the name of the project so that it corresponds with the folder by changing the
           name attribute in the package section in Cargo.toml, but it’s not something you need
           to do for the example to work
         Note
         You can find this example in this book’s GitHub repository in the ch10/b-coroutines-
         references folder.
      This time, we’ll learn how to store references to variables in our coroutines by using the following
      coroutine/wait example program:
        use std::fmt::Write;
        coroutine fn async_main() {
                                                                    Improving our example 2 – references     223
      let mut buffer = String::from("\nBUFFER:\n----\n");
      let writer = &mut buffer;
      println!("Program starting");
      let txt = http::Http::get("/600/HelloAsyncAwait").wait;
      writeln!(writer, "{txt}").unwrap();
      let txt = http::Http::get("/400/HelloAsyncAwait").wait;
      writeln!(writer, "{txt}").unwrap();
      println!("{}", buffer);

So, in this example, we create a buffer variable of the String type that we initialize with some text, and we take a &mut reference to that and store it in a writer variable. Every time we receive a response, we write the response to the buffer through the &mut reference we hold in writer before we print the buffer to the terminal at the end of the program. Let’s take a look at what we need to do to get this working. The first thing we do is pull in the fmt::Write trait so that we can write to our buffer using the writeln! macro. Add this to the top of main.rs:

ch09/b-coroutines-references/src/main.rs use std::fmt::Write;

Next, we need to change our Stack0 struct so that it represents what we must store across wait points in our updated example:

 ch09/b-coroutines-references/src/main.rs
  #[derive(Default)]
  struct Stack0 {
      buffer: Option<String>,
      writer: Option<*mut String>,

An important thing to note here is that writer can’t be Option<&mut String> since we know it will be referencing the buffer field in the same struct. A struct where a field takes a reference on &self is called a self-referential struct and there is no way to represent that in Rust since the lifetime of the self-reference is impossible to express. The solution is to cast the &mut self-reference to a pointer instead and ensure that we manage the lifetimes correctly ourselves.

224 Coroutines, Self-Referential Structs, and Pinning

      The only other thing we need to change is the Future::poll implementation:
      ch09/b-coroutines-references/src/main.rs
        State0::Start => {
                            // initialize stack (hoist variables)
                            self.stack.buffer = Some(String::from("\nBUFFER:\
        n----\n"));
                            self.stack.writer = Some(self.stack.buffer.as_
        mut().unwrap());
                            // ---- Code you actually wrote ----
                            println!("Program starting");
                            // ---------------------------------
                            let fut1 = Box::new(http::Http::get("/600/
        HelloAsyncAwait"));
                            self.state = State0::Wait1(fut1);
                            // save stack
      Okay, so this looks a bit odd. The first line we change is pretty straightforward. We initialize our
      buffer variable to a new String type, just like we did at the top of our coroutine/wait program.
      The next line, however, looks a bit dangerous.
      We cast the &mut reference to our buffer to a *mut pointer.
         Important
         Yes, I know we could have chosen another way of doing this since we can take a reference to
         buffer everywhere we need to instead of storing it in its variable, but that’s only because our
         example is very simple. Imagine that we use a library that needs to borrow data that’s local to
         the async function and we somehow have to manage the lifetimes manually like we do here
         but in a much more complex scenario.
      The self.stack.buffer.as_mut().unwrap() line returns a &mut reference to the buffer
      field. Since self.stack.writer is of the Option<*mut String> type, the reference will
      be coerced to a pointer (meaning that Rust does this cast implicitly by inferring it from the context).
         Note
         We take *mut String here since we deliberately don’t want a string slice (&str), which is
         often what we get (and want) when using a reference to a String type in Rust.
                                                                    Improving our example 2 – references   225

Let’s take a look at what happens after the first wait point:

ch09/b-coroutines-references/src/main.rs
  State0::Wait1(ref mut f1) => {
                      match f1.poll(waker) {
                          PollState::Ready(txt) => {
                              // Restore stack
                              let writer = unsafe { &mut *self.stack.
  writer.take().unwrap() };
                              // ---- Code you actually wrote ----
                              writeln!(writer, «{txt}»).unwrap();
                              // ---------------------------------
                              let fut2 = Box::new(http::Http::get("/400/
  HelloAsyncAwait"));
                              self.state = State0::Wait2(fut2);
                              // save stack
                              self.stack.writer = Some(writer);
                          PollState::NotReady => break
  PollState::NotReady,

The first change we make is regarding how we restore our stack. We need to restore our writer variable so that it holds a &mut String type that points to our buffer. To do this, we have to write some unsafe code that dereferences our pointer and lets us take a &mut reference to our buffer.

Note Casting a reference to a pointer is safe. The unsafe part is dereferencing the pointer.

Next, we add the line of code that writes the response. We can keep this the same as how we wrote it in our coroutine/wait function. Lastly, we save the stack state back since we need both variables to live across the wait point.

Note We don’t have to take ownership over the pointer stored in the writer field to use it since we can simply copy it, but to be somewhat consistent, we take ownership over it, just like we did in the first example. It also makes sense since if there is no need to store the pointer for the next await point, we can simply let it go out of scope by not storing it back.

226 Coroutines, Self-Referential Structs, and Pinning

      The last part is when we’ve reached Wait2 and our future returns PollState::Ready:
        State0::Wait2(ref mut f2) => {
                            match f2.poll(waker) {
                                PollState::Ready(txt) => {
                                    // Restore stack
                                    let buffer = self.stack.buffer.as_ref().
        take().unwrap();
                                    let writer = unsafe { &mut *self.stack.
        writer.take().unwrap() };
                                    // ---- Code you actually wrote ----
                                    writeln!(writer, «{txt}»).unwrap();
                                    println!("{}", buffer);
                                    // ---------------------------------
                                    self.state = State0::Resolved;
                          // Save stack / free resources
                          let _ = self.stack.buffer.take();
                                    break PollState::Ready(String::new());
                                PollState::NotReady => break
        PollState::NotReady,
      In this segment, we restore both variables since we write the last response through our writer variable,
      and then print everything that’s stored in our buffer to the terminal.
      I want to point out that the println!("{}", buffer); line takes a reference in the original
      coroutine/wait example, even though it might look like we pass in an owned String. Therefore, it
      makes sense that we restore the buffer to a &String type, and not the owned version. Transferring
      ownership would also invalidate the pointer in our writer variable.
      The last thing we do is drop the data we don’t need anymore. Our self.stack.writer field is
      already set to None since we took ownership over it when we restored the stack at the start, but we
      need to take ownership over the String type that self.stack.buffer holds as well so that it
      gets dropped at the end of this scope too. If we didn’t do that, we would hold on to the memory that’s
      been allocated to our String until the entire coroutine is dropped (which could be much later).
      Now, we’ve made all our changes. If the rewrites we did previously were implemented in corofy,
      our coroutine/wait implementation could, in theory, support much more complex use cases.
                                                      Improving our example 3 – this is… not… good…     227

Let’s take a look at what happens when we run our program by writing cargo run:

Program starting FIRST POLL - START OPERATION main: 1 pending tasks. Sleep until notified. FIRST POLL - START OPERATION main: 1 pending tasks. Sleep until notified.

BUFFER: HTTP/1.1 200 OK content-length: 15 connection: close content-type: text/plain; charset=utf-8 date: Thu, 30 Nov 2023 22:48:11 GMT

HelloAsyncAwait HTTP/1.1 200 OK content-length: 15 connection: close content-type: text/plain; charset=utf-8 date: Thu, 30 Nov 2023 22:48:11 GMT

HelloAsyncAwait

main: All tasks are finished

Puh, great. All that dangerous unsafe turned out to work just fine, didn’t it? Good job. Let’s make one small improvement before we finish.

Improving our example 3 – this is… not… good… Pretend you haven’t read this section title and enjoy the fact that our previous example compiled and showed the correct result. I think our coroutine implementation is so good now that we can look at some optimizations instead. There is one optimization in our executor in particular that I want to do immediately. Before we get ahead of ourselves, let’s set everything up:

   • Create a new folder called c-coroutines-problem and copy everything from
     b-coroutines-references over to it
   • You can change the name of the project so that it corresponds with the folder by changing the
     name attribute in the package section in Cargo.toml, but it’s not something you need
     to do for the example to work

228 Coroutines, Self-Referential Structs, and Pinning

         Tip
         This example is located in this book’s GitHub repository in the ch09/c-coroutines-
         problem folder.
      With that, everything has been set up.
      Back to the optimization. You see, new insights into the workload our runtime will handle in real life
      indicate that most futures will return Ready on the first poll. So, in theory, we can just poll the future
      we receive in block_on once and it will resolve immediately most of the time.
      Let’s navigate to src/runtime/executor.rs and take a look at how we can take advantage of
      this by adding a few lines of code.
      If you navigate to our Executor::block_on function, you’ll see that the first thing we do is
      spawn the future before we poll it. Spawning the future means that we allocate space for it in the
      heap and store the pointer to its location in a HashMap variable.
      Since the future will most likely return Ready on the first poll, this is unnecessary work that could
      be avoided. Let’s add this little optimization at the start of the block_on function to take advantage
      of this:
        pub fn block_on<F>(&mut self, future: F)
            where
                F: Future<Output = String> + 'static,
            {
                // ===== OPTIMIZATION, ASSUME READY
                let waker = self.get_waker(usize::MAX);
                let mut future = future;
                match future.poll(&waker) {
                    PollState::NotReady => (),
                    PollState::Ready(_) => return,
                // ===== END
                spawn(future);
                loop {
                    …
      Now, we simply poll the future immediately, and if the future resolves on the first poll, we return since
      we’re all done. This way, we only spawn the future if it’s something we need to wait on.
      Yes, this assumes we never reach usize::MAX for our IDs, but let’s pretend this is only a proof of
      concept. Our Waker will be discarded and replaced by a new one if the future is spawned and polled
      again anyway, so that shouldn’t be a problem.
                                                                       Discovering self-referential structs   229

Let’s try to run our program and see what we get:

Program starting FIRST POLL - START OPERATION main: 1 pending tasks. Sleep until notified. FIRST POLL - START OPERATION main: 1 pending tasks. Sleep until notified. /400/HelloAsyn free(): double free detected in tcache 2 Aborted

Wait, what?!? That doesn’t sound good! Okay, that’s probably a kernel bug in Linux, so let’s try it on Windows instead:

… error: process didn't exit successfully: `target\release\c-coroutinesproblem.exe` (exit code: 0xc0000374, STATUS_HEAP_CORRUPTION)

That sounds even worse!! What happened here? Let’s take a closer look at exactly what happened with our async system when we made our small optimization.

Discovering self-referential structs What happened is that we created a self-referential struct, initialized it so that it took a pointer to itself, and then moved it. Let’s take a closer look:

  1.   First, we received a future object as an argument to block_on. This is not a problem since the
       future isn’t self-referential yet, so we can move it around wherever we want to without issues
       (this is also why moving futures before they’re polled is perfectly fine using proper async/await).
  2.   Then, we polled the future once. The optimization we did made one essential change. The
       future was located on the stack (inside the stack frame of our block_on function) when we
       polled it the first time.
  3.   When we polled the future the first time, we initialized the variables to their initial state. Our
       writer variable took a pointer to our buffer variable (stored as a part of our coroutine)
       and made it self-referential at this point.
  4.   The first time we polled the future, it returned NotReady
  5.   Since it returned NotReady, we spawned the future, which moves it into the tasks collection
       with the HashMap<usize, Box<dyn Future<Output = String>>> type in our
       Executor. The future is now placed in Box, which moves it to the heap.

230 Coroutines, Self-Referential Structs, and Pinning

        6.   The next time we poll the future, we restore the stack by dereferencing the pointer we hold for
             our writer variable. However, there’s a big problem: the pointer is now pointing to the old
             location on the stack where the future was located at the first poll.
        7.   That can’t end well, and it doesn’t in our case.
      You’ve now seen firsthand the problem with self-referential structs, how this applies to futures, and
      why we need something that prevents this from happening.
      A self-referential struct is a struct that takes a reference to self and stores it in a field. Now, the term
      reference here is a little bit unprecise since there is no way to take a reference to self in Rust and store
      that reference in self. To do this in safe Rust, you have to cast the reference to a pointer (remember
      that references are just pointers with a special meaning in the programming language).
         Note
         When we create visualizations in this chapter, we’ll disregard padding, even though we know
         structs will likely have some padding between fields, as we discussed in Chapter 4.
      When this value is moved to another location in memory, the pointer is not updated and points to
      the “old” location.
      If we take a look at a move from one location on the stack to another one, it looks something like this:
                                      Figure 9.1 – Moving a self-referential struct
      In the preceding figure, we can see the memory addresses to the left with a representation of the stack
      next to it. Since the pointer was not updated when the value was moved, it now points to the old
      location, which can cause serious problems.
Figure from page 251
figure · book page 251
                                                                       Discovering self-referential structs   231

Note It can be very hard to detect these issues, and creating simple examples where a move like this causes serious issues is surprisingly difficult. The reason for this is that even though we move everything, the old values are not zeroed or overwritten immediately. Often, they’re still there, so dereferencing the preceding pointer would probably produce the correct value. The problem only arises when you change the value of x in the new location, and expect y to point to it. Dereferencing y still produces a valid value in this case, but it’s the wrong value. Optimized builds often optimize away needless moves, which can make bugs even harder to detect since most of the program will seem to work just fine, even though it contains a serious bug.

What is a move? A move in Rust is one of those concepts that’s unfamiliar to many programmers coming from C#, Javascript, and similar garbage-collected languages, and different from what you’re used to for C and C++ programmers. The definition of move in Rust is closely related to its ownership system. Moving means transferring ownership. In Rust, a move is the default way of passing values around and it happens every time you change ownership over an object. If the object you move only consists of copy types (types that implement the Copy trait), this is as simple as copying the data over to a new location on the stack. For non-copy types, a move will copy all copy types that it contains over just like in the first example, but now, it will also copy pointers to resources such as heap allocations. The moved-from object is left inaccessible to us (for example, if you try to use the moved-from object, the compilation will fail and let you know that the object has moved), so there is only one owner over the allocation at any point in time. In contrast to cloning, it does not recreate any resources and make a clone of them. One more important thing is that the compiler makes sure that drop is never called on the movedfrom object so that the only thing that can free the resources is the new object that took ownership over everything. Figure 9.2 provides a simplified visual overview of the difference between move, clone, and copy (we’ve excluded any internal padding of the struct in this visualization). Here, we assume that we have a struct that holds two fields – a copy type, a, which is an i64 type, and a non-copy type, b, which is a Vec<u8> type:

232 Coroutines, Self-Referential Structs, and Pinning

                                          Figure 9.2 – Move, clone, and copy
      A move will in many ways be like a deep copy of everything in our struct that’s located on the stack.
      This is problematic when you have a pointer that points to self, like we have with self-referential
      structs, since self will start at a new memory address after the move but the pointer to self won’t
      be adjusted to reflect that change.
      Most of the time, when programming Rust, you probably won’t think a lot about moves since it’s part
      of the language you never explicitly use, but it’s important to know what it is and what it does.
      Now that we’ve got a good understanding of what the problem is, let’s take a closer look at how Rust
      solves this by using its type system to prevent us from moving structs that rely on a stable place in
      memory to function correctly.
Figure from page 253
figure · book page 253
                                                                                        Pinning in Rust   233

Pinning in Rust The following diagram shows a slightly more complex self-referential struct so that we have something visual to help us understand:

                      Figure 9.3 – Moving a self-referential struct with three fields

At a very high level, pinning makes it possible to rely on data that has a stable memory address by disallowing any operation that might move it:

                                  Figure 9.4 – Moving a pinned struct

The concept of pinning is pretty simple. The complex part is how it’s implemented in the language and how it’s used.

Figure from page 254
figure · book page 254
Figure from page 254
figure · book page 254

234 Coroutines, Self-Referential Structs, and Pinning

      Pinning in theory
      Pinning is a part of Rust’s standard library and consists of two parts: the type, Pin, and the marker-
      trait, Unpin. Pinning is only a language construct. There is no special kind of location or memory
      that you move values to so they get pinned. There is no syscall to ask the operating system to ensure
      a value stays the same place in memory. It’s only a part of the type system that’s designed to prevent
      us from being able to move a value.
      Pin does not remove the need for unsafe – it just gives the user of unsafe a guarantee that the
      value has a stable location in memory, so long as the user that pinned the value only uses safe Rust.
      This allows us to write self-referential types that are safe. It makes sure that all operations that can
      lead to problems must use unsafe.
      Back to our coroutine example, if we were to move the struct, we’d have to write unsafe Rust. That
      is how Rust upholds its safety guarantee. If you somehow know that the future you created never
      takes a self-reference, you could choose to move it using unsafe, but the blame now falls on you if
      you get it wrong.
      Before we dive a bit deeper into pinning, we need to define several terms that we’ll need going forward.
      Definitions
      Here are the definitions we must understand:
         • Pin<T> is the type it’s all about. You’ll find this as a part of Rust’s standard library under the
           std::pin module. Pin wrap types that implement the Deref trait, which in practical terms
           means that it wraps references and smart pointers.
         • Unpin is a marker trait. If a type implements Unpin, pinning will have no effect on that type.
           You read that right – no effect. The type will still be wrapped in Pin but you can simply take
           it out again.
             The impressive thing is that almost everything implements Unpin by default, and if you manually
             want to mark a type as !Unpin, you have to add a marker trait called PhantomPinned
             to your type. Having a type, T, implement !Unpin is the only way for something such as
             Pin<&mut T> to have any effect.
         • Pinning a type that’s !Unpin will guarantee that the value remains at the same location in
           memory until it gets dropped, so long as you stay in safe Rust.
         • Pin projections are helper methods on a type that’s pinned. The syntax often gets a little weird
           since they’re only valid on pinned instances of self. For example, they often look like fn
           foo(self: Pin<&mut self>).
                                                                                         Pinning in Rust   235
   • Structural pinning is connected to pin projections in the sense that, if you have Pin<&mut
     T> where T has one field, a, that can be moved freely and one that can’t be moved, b, you can
     do the following:
       ‚ Write a pin projection for a with the fn a(self: Pin<&mut self>) -> &A signature.
         In this case, we say that pinning is not structural.
       ‚ Write a projection for b that looks like fn b(self: Pin<&mut self>) -> Pin<&mut
         B>, in which case we say that pinning is structural for b since it’s pinned when the struct,
         T, is pinned.

With the most important definitions out of the way, let’s look at the two ways we can pin a value.

Pinning to the heap

Note The small code snippets we’ll present here can be found in this book’s GitHub repository in the ch09/d-pin folder. The different examples are implemented as different methods that you comment/uncomment in the main function.

Let’s write a small example to illustrate the different ways of pinning a value:

ch09/d-pin/src/main.rs use std::{marker::PhantomPinned, pin::Pin};

  #[derive(Default)]
  struct MaybeSelfRef {
      a: usize,
      b: Option<*const usize>,
      _pin: PhantomPinned,

So, we want to be able to create an instance using MaybeSelfRef::default() that we can move around as we wish, but then at some point initialize it to a state where it references itself; moving it would cause problems.

236 Coroutines, Self-Referential Structs, and Pinning

      This is very much like futures that are not self-referential until they’re polled, as we saw in our previous
      example. Let's write the impl block for MaybeSelfRef and take a look at the code::
      ch09/d-pin/src/main.rs
        impl MaybeSelfRef {
            fn init(self: Pin<&mut Self>) {
                unsafe {
                    let Self { a, b, .. } = self.get_unchecked_mut();
                    *b = Some(a);
            fn b(self: Pin<&mut Self>) -> Option<&mut usize> {
                unsafe { self.get_unchecked_mut().b.map(|b| &mut *b) }
      As you can see, MaybeStelfRef will only be self-referential after we call init on it.
      We also define one more method that casts the pointer stored in b to Option<&mut usize>,
      which is a mutable reference to a.
      One thing to note is that both our functions require unsafe. Without Pin, the only method requiring
      unsafe would be b since we dereference a pointer there. Acquiring a mutable reference to a pinned
      value always require unsafe, since there is nothing preventing us from moving the pinned value
      at that point.
      Pinning to the heap is usually done by pinning a Box. There is even a convenient method on Box
      that allows us to get Pin<Box<...>>. Let’s look at a short example:
      ch09/d-pin/src/main.rs
        fn main() {
            let mut x = Box::pin(MaybeSelfRef::default());
            x.as_mut().init();
            println!("{}", x.as_ref().a);
            *x.as_mut().b().unwrap() = 2;
            println!("{}", x.as_ref().a);
                                                                                            Pinning in Rust    237

Here, we pin MaybeSelfRef to the heap and initialize it. We print out the value of a and then mutate the data through the self-reference in b, and set its value to 2. If we look at the output, we’ll see that everything looks as expected:

      Finished dev [unoptimized + debuginfo] target(s) in 0.56s
       Running `target\debug\x-pin-experiments.exe`

The pinned value can never move and as users of MaybeSelfRef, we didn’t have to write any unsafe code. Rust can guarantee that we never (in safe Rust) get a mutable reference to MaybeSelfRef since Box took ownership of it. Heap pinning being safe is not so surprising since, in contrast to the stack, a heap allocation will be stable throughout the program, regardless of where we create it.

Important This is the preferred way to pin values in Rust. Stack pinning is for those cases where you don’t have a heap to work with or can’t accept the cost of that extra allocation.

Let’s take a look at stack pinning while we’re at it.

Pinning to the stack Pinning to the stack can be somewhat difficult. In Chapter 5, we saw how the stack worked and we know that it grows and shrinks as values are popped and pushed to the stack. So, if we’re going to pin to the stack, we have to pin it somewhere “high” on the stack. This means that if we pin a value to the stack inside a function call, we can’t return from that function, and expect the value to still be pinned there. That would be impossible. Pinning to the stack is hard since we pin by taking &mut T, and we have to guarantee that we won’t move T until it’s dropped. If we’re not careful, this is easy to get wrong. Rust can’t help us here, so it’s up to us to uphold that guarantee. This is why stack pinning is unsafe. Let’s look at the same example using stack pinning:

ch09/d-pin/src/main.rs
  fn stack_pinning_manual() {
      let mut x = MaybeSelfRef::default();
      let mut x = unsafe { Pin::new_unchecked(&mut x) };
      x.as_mut().init();
      println!("{}", x.as_ref().a);

238 Coroutines, Self-Referential Structs, and Pinning

            *x.as_mut().b().unwrap() = 2;
            println!("{}", x.as_ref().a);
      The noticeable difference here is that it’s unsafe to pin to the stack, so now, we need unsafe both
      as users of MaybeSelfRef and as implementors.
      If we run the example with cargo run, the output will be the same as in our first example:
            Finished dev [unoptimized + debuginfo] target(s) in 0.58s
             Running `target\debug\x-pin-experiments.exe`
      The reason stack pinning requires unsafe is that it’s rather easy to accidentally break the guarantees
      that Pin is supposed to provide. Let’s take a look at this example:
      ch09/d-pin/src/main.rs
        use std::mem::swap;
        fn stack_pinning_manual_problem() {
            let mut x = MaybeSelfRef::default();
            let mut y = MaybeSelfRef::default();
            {
                let mut x = unsafe { Pin::new_unchecked(&mut x) };
                x.as_mut().init();
                *x.as_mut().b().unwrap() = 2;
            swap(&mut x, &mut y);
            println!("
             x: {{
          +----->a: {:p},
          |      b: {:?},
          |  }}
          |  y: {{
          |      a: {:p},
          +-----|b: {:?},
             }}",
                &x.a,
                x.b,
                &y.a,
                y.b,
                                                                                          Pinning in Rust    239
      );

In this example, we create two instances of MaybeSelfRef called x and y. Then, we create a scope where we pin x and set the value of x.a to 2 by dereferencing the self-reference in b, as we did previously. Now, when we exit the scope, x isn’t pinned anymore, which means we can take a mutable reference to it without needing unsafe. Since this is safe Rust and we should be able to do what we want, we swap x and y. The output prints out the pointer address of the a field of both structs and the value of the pointer stored in b. When we look at the output, we should see the problem immediately:

  Finished dev [unoptimized + debuginfo] target(s) in 0.58s
       Running `target\debug\x-pin-experiments.exe`
       x: {
    +----->a: 0xe45fcff558,
    |      b: None,
    |  }
    |  y: {
    |      a: 0xe45fcff570,
    +-----|b: Some(0xe45fcff558),

Although the pointer values will differ from run to run, it’s pretty evident that y doesn’t hold a pointer to self anymore. Right now, it points somewhere in x. This is very bad and will cause the exact memory safety issues Rust is supposed to prevent.

Note For this reason, the standard library has a pin! macro that helps us with safe stack pinning. The macro uses unsafe under the hood but makes it impossible for us to reach the pinned value again.

Now that we’ve seen all the pitfalls of stack pinning, my clear recommendation is to avoid it unless you need to use it. If you have to use it, then use the pin! macro so that you avoid the issues we’ve described here.

240 Coroutines, Self-Referential Structs, and Pinning

         Tip
         In this book’s GitHub repository, you’ll find a function called stack_pinning_macro()
         in the ch09/d-pin/src/main.rs file. This function shows the preceding example but
         using Rust’s pin! macro.
      Pin projections and structural pinning
      Before we leave the topic of pinning, we’ll quickly explain what pin projections and structural pinning
      are. Both sound complex, but they are very simple in practice. The following diagram shows how
      these terms are connected:
                                   Figure 9.5 – Pin projection and structural pinning
      Structural pinning means that if a struct is pinned, so is the field. We expose this through pin projections,
      as we’ll see in the following code example.
      If we continue with our example and create a struct called Foo that holds both MaybeSelfRef
      (field a) and a String type (field b), we could write two projections that return a pinned version of
      a and a regular mutable reference to b:
      ch09/d-pin/src/main.rs
            #[derive(Default)]
            struct Foo {
                a: MaybeSelfRef,
                b: String,
Figure from page 261
figure · book page 261
                                                      Improving our example 4 – pinning to the rescue   241
      impl Foo {
          fn a(self: Pin<&mut Self>) -> Pin<&mut MaybeSelfRef> {
              unsafe {
                  self.map_unchecked_mut(|s| &mut s.a)
          fn b(self: Pin<&mut Self>) -> &mut String {
              unsafe {
                  &mut self.get_unchecked_mut().b

Note that these methods will only be callable when Foo is pinned. You won’t be able to call either of these methods on a regular instance of Foo. Pin projections do have a few subtleties that you should be aware of, but they’re explained in quite some detail in the official documentation (https://doc.rust-lang.org/stable/std/ pin/index.html), so I’ll refer you there for more information about the precautions you must take when writing projections.

Note Since pin projections can be a bit error-prone to create yourself, there is a popular create for making pin projections called pin_project (https://docs.rs/pin-project/latest/ pin_project/). If you ever end up having to make pin projections, it’s worth checking out.

With that, we’ve pretty much covered all the advanced topics in async Rust. However, before we go on to our last chapter, let’s see how pinning will prevent us from making the big mistake we made in the last iteration of our coroutine example.

Improving our example 4 – pinning to the rescue Fortunately, the changes we need to make are small, but before we continue and make the changes, let’s create a new folder and copy everything we had in our previous example over to that folder:

• Copy the entire c-coroutines-problem folder and name the new copy e-coroutinespin • Open Cargo.toml and rename the name of the package e-coroutines-pin

242 Coroutines, Self-Referential Structs, and Pinning

         Tip
         You’ll find the example code we’ll go through here in this book’s GitHub repository under the
         ch09/e-coroutines-pin folder.
      Now that we have a new folder set up, let’s start making the necessary changes. The logical place to
      start is our Future definition in future.rs.
      future.rs
      The first thing we’ll do is pull in Pin from the standard library at the very top:
      ch09/e-coroutines-pin/src/future.rs
        use std::pin::Pin;
      The only other change we need to make is in the definition of poll in our Future trait:
        fn poll(self: Pin<&mut Self>, waker: &Waker) ->
        PollState<Self::Output>;
      That’s pretty much it.
      However, the implications of this change are noticeable pretty much everywhere poll is called, so we
      need to fix that as well.
      Let’s start with http.rs.
      http.rs
      The first thing we need to do is pull in Pin from the standard library. The start of the file should
      look like this:
      ch09/e-coroutines-pin/src/http.rs
        use crate::{future::PollState, runtime::{self, reactor, Waker},
        Future};
        use mio::Interest;
        use std::{io::{ErrorKind, Read, Write}, pin::Pin};
      The only other place we need to make some changes is in the Future implementation for
      HttpGetFuture, so let’s locate that. We’ll start by changing the arguments in poll:
      ch09/e-coroutines-pin/src/http.rs
        fn poll(mut self: Pin<&mut Self>, waker: &Waker) ->
        PollState<Self::Output>
                                                      Improving our example 4 – pinning to the rescue   243

Since self is now Pin<&mut Self>, there are several small changes we need to make so that the borrow checker stays happy. Let’s start from the top:

ch09/e-coroutines-pin/src/http.rs
  let id = self.id;
          if self.stream.is_none() {
              println!("FIRST POLL - START OPERATION");
              self.write_request();
              let stream = (&mut self).stream.as_mut().unwrap();
              runtime::reactor().register(stream, Interest::READABLE,
  id);
              runtime::reactor().set_waker(waker, self.id);

The reason for assigning id to a variable at the top is that the borrow checker gives us some minor trouble when trying to pass in both &mut self and &self as arguments to the register/deregister functions, so we just assign id to a variable at the top and everyone is happy. There are only two more lines to change, and that is where we create a String type from our internal buffer and deregister interest with the reactor:

ch09/e-coroutines-pin/src/http.rs let s = String::from_utf8_lossy(&self.buffer).to_string(); runtime::reactor().deregister(self.stream.as_mut().unwrap(), id); break PollState::Ready(s);

Important Notice that this future is Unpin. There is nothing that makes it unsafe to move HttpGetFuture around, and this is indeed the case for most futures like this. Only the ones created by async/await are self-referential by design. That means there is no need for any unsafe here.

Next, let’s move on to main.rs since there are some important changes we need to make there.

244 Coroutines, Self-Referential Structs, and Pinning

      Main.rs
      Let’s start from the top and make sure we have the correct imports:
      ch09/e-coroutines-pin/src/main.rs
        mod future;
        mod http;
        mod runtime;
        use future::{Future, PollState};
        use runtime::Waker;
        use std::{fmt::Write, marker::PhantomPinned, pin::Pin};
      This time, we need both the PhantomPinned marker and Pin.
      The next thing we need to change is in our State0 enum. The futures we hold between states are
      now pinned:
      ch09/e-coroutines-pin/src/main.rs
         Wait1(Pin<Box<dyn Future<Output = String>>>),
         Wait2(Pin<Box<dyn Future<Output = String>>>),
      Next up is an important change. We need to make our coroutines !Unpin so that they can’t be moved
      once they have been pinned. We can do this by adding a marker trait to our Coroutine0 struct:
      ch09/e-coroutines-pin/src/main.rs
        struct Coroutine0 {
            stack: Stack0,
            state: State0,
            _pin: PhantomPinned,
      We also need to add the PhantomPinned marker to our new function:
      ch09/e-coroutines-pin/src/main.rs
        impl Coroutine0 {
            fn new() -> Self {
                Self {
                    state: State0::Start,
                    stack: Stack0::default(),
                    _pin: PhantomPinned,
                                                          Improving our example 4 – pinning to the rescue      245

The last thing we need to change is the poll method. Let’s start with the function signature:

ch09/e-coroutines-pin/src/main.rs fn poll(self: Pin<&mut Self>, waker: &Waker) -> PollState<Self::Output>

The easiest way I found to change our code was to simply define a new variable at the very top of the function called this, which replaces self everywhere in the function body. I won’t go through every line since the change is so trivial, but after the first line, it’s a simple search and replace everywhere self was used earlier, and change it to this:

ch09/e-coroutines-pin/src/main.rs
  let this = unsafe { self.get_unchecked_mut() };
          loop {
              match this.state {
                  State0::Start => {
                      // initialize stack (hoist declarations - no stack
  yet)
                      this.stack.buffer = Some(String::from("\nBUFFER:\
  n----\n"));
                      this.stack.writer = Some(this.stack.buffer.as_
  mut().unwrap());
                      // ---- Code you actually wrote ----
                      println!("Program starting");

The important line here was let this = unsafe { self.get_unchecked_mut() };. Here, we had to use unsafe since the pinned value is !Unpin because of the marker trait we added. Getting to the pinned value is unsafe since there is no way for Rust to guarantee that we won’t move the pinned value. The nice thing about this is that if we encounter any such problems later, we know we can search for the places where we used unsafe and that the problem must be there.

246 Coroutines, Self-Referential Structs, and Pinning

      The next thing we need to change is to have the futures we store in our wait states pinned. We can do
      this by calling Box::pin instead of Box::new:
      ch09/e-coroutines-pin/src/main.rs
        let fut1 = Box::pin(http::Http::get("/600/HelloAsyncAwait"));
        let fut2 = Box::pin(http::Http::get("/400/HelloAsyncAwait"));
      The last place in main.rs where we need to make changes is in the locations where we poll our child
      futures since we now have to go through the Pin type to get a mutable reference:
      ch09/e-coroutines-pin/src/main.rs
        match f1.as_mut().poll(waker)
        match f2.as_mut().poll(waker)
      Note that we don’t need unsafe here since these futures are !Unpin.
      The last place we need to change a few lines of code is in executor.rs, so let’s head over there as
      our last stop.
      executor.rs
      The first thing we must do is make sure our dependencies are correct. The only change we’re making
      here is adding Pin from the standard library:
      ch09/e-coroutines-pin/src/runtime/executor.rs
            thread::{self, Thread}, pin::Pin,
        };
      The next line we’ll change is our Task type alias so that it now refers to Pin<Box<…>>:
        type Task = Pin<Box<dyn Future<Output = String>>>;
      The last line we’ll change for now is in our spawn function. We have to pin the futures to the heap:
        e.tasks.borrow_mut().insert(id, Box::pin(future));
      If we try to run our example now, it won’t even compile and give us the following error:
        error[E0599]: no method named `poll` found for struct `Pin<Box<dyn
        future::Future<Output = String>>>` in the current scope
          --> src\runtime\executor.rs:89:30
                                                        Improving our example 4 – pinning to the rescue    247

It won’t even let us poll the future anymore without us pinning it first since poll is only callable for Pin<&mut Self> types and not &mut self anymore. So, we have to decide whether we pin the value to the stack or the heap before we even try to poll it. In our case, our whole executor works by heap allocating futures, so that’s the only thing that makes sense to do. Let’s remove our optimization entirely and change one line of code to make our executor work again:

ch09/e-coroutines-pin/src/runtime/executor.rs match future.as_mut().poll(&waker) {

If you try to run the program again by writing cargo run, you should get the expected output back and not have to worry about the coroutine/wait generated futures being moved again (the output has been abbreviated slightly):

Finished dev [unoptimized + debuginfo] target(s) in 0.02s Running `target\debug\e-coroutines-pin.exe` Program starting FIRST POLL - START OPERATION main: 1 pending tasks. Sleep until notified. FIRST POLL - START OPERATION main: 1 pending tasks. Sleep until notified.

BUFFER: HTTP/1.1 200 OK content-length: 15 [=== ABBREVIATED ===] date: Sun, 03 Dec 2023 23:18:12 GMT

HelloAsyncAwait

main: All tasks are finished

You now have self-referential coroutines that can safely store both data and references across wait points. Congratulations! Even though making these changes took up quite a few pages, the changes themselves were part pretty trivial for the most part. Most of the changes were due to Pin having a different API than what we had when using references before. The good thing is that this sets us up nicely for migrating our whole runtime over to futures created by async/await instead of our own futures created by coroutine/wait with very few changes.

248 Coroutines, Self-Referential Structs, and Pinning

      Summary
      What a ride, huh? If you’ve got to the end of this chapter, you’ve done a fantastic job, and I have good
      news for you: you pretty much know everything about how Rust’s futures work and what makes them
      special already. All the complicated topics are covered.
      In the next, and last, chapter, we’ll switch over from our hand-made coroutines to proper async/await.
      This will seem like a breeze compared to what you’ve gone through so far.
      Before we continue, let’s stop for a moment and take a look at what we’ve learned in this chapter.
      First, we expanded our coroutine implementation so that we could store variables across wait points.
      This is pretty important if our coroutine/wait syntax is going to rival regular synchronous code in
      readability and ergonomics.
      After that, we learned how we could store and restore variables that held references, which is just as
      important as being able to store data.
      Next, we saw firsthand something that we’ll never see in Rust unless we implement an asynchronous
      system, as we did in this chapter (which is quite the task just to prove a single point). We saw how
      moving coroutines that hold self-references caused serious memory safety issues, and exactly why
      we need something to prevent them.
      That brought us to pinning and self-referential structs, and if you didn’t know about these things
      already, you do now. In addition to that, you should at least know what a pin projection is and what
      we mean by structural pinning.
      Then, we looked at the differences between pinning a value to the stack and pinning a value to the
      heap. You even saw how easy it was to break the Pin guarantee when pinning something to the stack
      and why you should be very careful when doing just that.
      You also know about some tools that are widely used to tackle both pin projections and stack pinning
      and make both much safer and easier to use.
      Next, we got firsthand experience with how we could use pinning to prevent the issues we had with
      our coroutine implementation.
      If we take a look at what we’ve built so far, that’s pretty impressive as well. We have the following:
         • A coroutine implementation we’ve created ourselves
         • Coroutine/wait syntax and a preprocessor that helps us with the boilerplate for our coroutines
         • Coroutines that can safely store both data and references across wait points
         • An efficient runtime that stores, schedules, and polls the tasks to completion
                                                                                                  Summary      249
   • The ability to spawn new tasks onto the runtime so that one task can spawn hundreds of new
     tasks that will run concurrently
   • A reactor that uses epoll/kqueue/IOCP under the hood to efficiently wait for and respond
     to new events reported by the operating system

I think this is pretty cool. We’re not quite done with this book yet. In the next chapter, you’ll see how we can have our runtime run futures created by async/await instead of our own coroutine implementation with just a few changes. This enables us to leverage all the advantages of async Rust. We’ll also take some time to discuss the state of async Rust today, the different runtimes you’ll encounter, and what we might expect in the future. All the heavy lifting is done now. Well done!

← / → change chapter. Esc returns to the main menu. Click a figure to zoom.