Rust 101 - 35: Futures

July 18, 2024 [Programming, Programming Languages, Rust, Tech, Videos]

Exploring what a Future is in async Rust and how we could manually write code that polls futures. Normally, we avoid this manual work by using the async and await keywords, but looking into this helps us understand what those keywords really do.

Series: Language basics, More syntax, Traits and generics, Building applications, Concurrency and parallelism, Trait objects, Async, Unsafe, FFI

This section (Async): 34: What is async?, 35: Futures, 36: async/await, 37: Runtimes, 38: Exercise E1a, 39: Exercise E1b, 40: Exercise E2a, 41: Exercise E2b

Links:

The course materials for this series are developed by tweede golf. You can find more information at github.com/tweedegolf/101-rs and you can sponsor the work at github.com/sponsors/tweedegolf. They are released under the Creative Commons Attribution Share Alike 4.0 International license.

This series of videos is copyright 2024 Andy Balaam and the tweede golf contributors and is released under the Creative Commons Attribution Share Alike 4.0 International license.

Comments

Tiger12506 2026-08-01

The key to understanding asynchronous functions (for me from a C# perspective) was a thought experiment. Let's say you call an async function, but that function takes 10 min to run. In the meantime, your code continues executing up until the next await. When the Future is decided (either with a Result or a timeout), your code the can continue on from that await.

This has a couple unexpected consequences:

  1. await is blocking. You are awaiting a result, forcing the Future to produce one before continuing this line of execution. async/await does not magically make parallelism happen.
  2. Because of this, using awaitt when calling an async function forces the caller to be an async function. If the code in the caller is meant to continue on, but is blocked by the await, then it is indeterminate when the *caller* will end, and therefore it has to be async as well.
  3. If you take that to the extreme, and you "pollute" your code-base with async functions (as tends to happen), then you can imagine a whole callstack of functions that have run off the end and returned a Future of a Future of a Future that are combined together with a Join, so that at the end of the day (in main, for example) you have one master Future over all of them. The main function *cannot* be async, your program has a blocking beginning and ending, so your main function literally must poll the final Future to reach completion.

And that's why async/await becomes a runtime --> there must be a monitor loop all the way back in the callstack which handles the final await, which controls the program flow, returning control back to the next line when an await occurs.

This sort of thing is why embedded devices now have runtimes as well. A RTOS is basically a giant async/await framework that helps to support programming with very asynchronous hardware (like networking). The ESP is a good example, because of the wifi chip, it needs an async framework so that it can await, and continue executing in the meantime.