Rust Programming Language
By pairing poll-based futures with the new Pin pointer to secure self-referential structures, Rust delivers ergonomic asynchronous programming that matches the performance of hand-written state machines.
AI-assisted summary of RustLatam 2019 - Without Boats: Zero-Cost Async IO — original source. Check the original for full context.
Source published:
Key ideas
- Rust abandoned its pre-1.0 green threads architecture because runtime-enforced schedulers imposed hidden overhead on all binaries and crippled seamless C interoperability, violating the principles of zero-cost abstractions. (7:36)
- Unlike callback-driven futures in other languages that demand pervasive heap allocations, Alex Crichton and Aaron Turon's poll-based future model enables the compiler to flatten async workflows into single-allocation enum state machines. (13:40)
- Holding references across await points generates self-referential structs that break under memory moves; the introduction of the Pin type solved this by guaranteeing immovable memory addresses without runtime cost. (26:17)
- Following the stabilization of Pin in Rust 1.33, the core Future trait and async/await syntax were slated for complete stabilization by mid-to-late 2019, paving the way for asynchronous Streams and general generators. (29:41)
Detailed notes
## Background Published on YouTube by the Rust Programming Language channel on April 22, 2019, this talk from RustLatam 2019 features without boats, a Senior Research Engineer at Mozilla Research and core member of the Rust language design team. According to the description, boats—who contributed to major Rust capabilities like generic associated types and const generics—focuses on the year-long design effort behind bringing first-class async/await syntax to non-blocking I/O in Rust.
## TL;DR In this keynote, without boats examines how Rust achieved zero-cost asynchronous I/O without sacrificing low-level systems control or requiring a heavy runtime. Boats charts the evolution from discarded pre-1.0 green threads and flawed callback-based futures to Alex Crichton and Aaron Turon's poll-based state machine model. The talk highlights the major usability crisis caused by self-referential structs when borrowing across suspension points and explains how the invention of the `Pin` type resolved this issue. Finally, boats presents the 2019 stabilization roadmap for async/await alongside future ambitions for async traits, streams, and language-level generators.
## Atmosphere & style The presentation possesses an earnest, lucid, and technically rigorous tone, delivered with unpretentious charm even as boats works through a slight cold. Candid reflections on community exasperation—such as early compiler error soup and users wishing Rust had garbage collection—give the architectural deep dive relatable emotional weight. The pacing feels collaborative and reflective of open-source language engineering at its best, concluding with an endearing, self-deprecating laugh over a minor slide glitch that garners warm applause.
## Bulletin - [07:36](https://www.youtube.com/watch?v=skos4B5x7qE&t=456s) Rust abandoned its pre-1.0 green threads architecture because runtime-enforced schedulers imposed hidden overhead on all binaries and crippled seamless C interoperability, violating the principles of zero-cost abstractions. - [13:40](https://www.youtube.com/watch?v=skos4B5x7qE&t=820s) Unlike callback-driven futures in other languages that demand pervasive heap allocations, Alex Crichton and Aaron Turon's poll-based future model enables the compiler to flatten async workflows into single-allocation enum state machines. - [26:17](https://www.youtube.com/watch?v=skos4B5x7qE&t=1577s) Holding references across await points generates self-referential structs that break under memory moves; the introduction of the Pin type solved this by guaranteeing immovable memory addresses without runtime cost. - [29:41](https://www.youtube.com/watch?v=skos4B5x7qE&t=1781s) Following the stabilization of Pin in Rust 1.33, the core Future trait and async/await syntax were slated for complete stabilization by mid-to-late 2019, paving the way for asynchronous Streams and general generators.
## Key takeaways - [01:55](https://www.youtube.com/watch?v=skos4B5x7qE&t=115s) The async/await feature set introduces two keywords—async and await—transforming functions into state machines returning Future types while retaining the linear ergonomics of blocking code. - [04:26](https://www.youtube.com/watch?v=skos4B5x7qE&t=266s) Rust adheres to Bjarne Stroustrup's definition of zero-cost abstractions: you do not pay for what you do not use, and hand-written low-level code cannot outperform the abstraction. - [05:59](https://www.youtube.com/watch?v=skos4B5x7qE&t=359s) Standard blocking I/O fails to scale under massive network connection counts due to operating system thread overhead and kernel context switching costs. - [07:36](https://www.youtube.com/watch?v=skos4B5x7qE&t=456s) Green threads were completely removed prior to Rust 1.0 because bundling a mandatory userspace scheduler imposed an unavoidable runtime footprint that blocked embedding Rust into existing C programs. - [10:35](https://www.youtube.com/watch?v=skos4B5x7qE&t=635s) Standard callback-based futures failed Rust's zero-cost guarantees because each chained step required dynamic dispatch and separate heap allocations (Box<dyn FnOnce>). - [13:40](https://www.youtube.com/watch?v=skos4B5x7qE&t=820s) The poll-based model inverts control: executors drive progress by actively polling futures, and reactors notify executors via Wakers only when underlying I/O events become ready. - [13:40](https://www.youtube.com/watch?v=skos4B5x7qE&t=820s) Poll-based futures offer trivial task cancellation, as simply dropping the future handle halts further execution without requiring cancellation tokens. - [13:40](https://www.youtube.com/watch?v=skos4B5x7qE&t=820s) Compiling poll-based workflows collapses nested stages into an enum-like state machine that shares memory across variants, requiring only a single heap allocation when spawned. - [18:43](https://www.youtube.com/watch?v=skos4B5x7qE&t=1123s) Early future combinators (like and_then and map) created deeply nested closures and sprawling type signatures that triggered impenetrable compiler diagnostic errors. - [21:09](https://www.youtube.com/watch?v=skos4B5x7qE&t=1269s) Awaiting a future desugars into a polling loop that yields Pending to the outer executor until a wake notification signals resumption. - [23:08](https://www.youtube.com/watch?v=skos4B5x7qE&t=1388s) Holding a reference across an await suspension point causes the generated state machine struct to hold a pointer to its own fields, creating a self-referential struct. - [23:08](https://www.youtube.com/watch?v=skos4B5x7qE&t=1388s) Because Rust standard moves use bitwise copies (memcpy) that leave internal pointers dangling, early futures strictly prohibited borrowing across await points. - [26:17](https://www.youtube.com/watch?v=skos4B5x7qE&t=1577s) The Pin pointer wrapper prevents values from moving in memory, allowing safe borrowing across await points without heap indirection or runtime overhead. - [26:17](https://www.youtube.com/watch?v=skos4B5x7qE&t=1577s) Rewriting Future::poll to accept Pin<&mut Self> instead of &mut Self anchored async state machines to fixed memory addresses, unlocking idiomatic Rust borrowing in async functions. - [29:41](https://www.youtube.com/watch?v=skos4B5x7qE&t=1781s) Following async/await stabilization, the Rust roadmap prioritized the Stream trait for multi-value asynchronous iteration, async methods in traits, and general language generators.
## Detailed walkthrough ### [00:07](https://www.youtube.com/watch?v=skos4B5x7qE&t=7s) Introduction and Welcoming the Global Rust Community
The talk opens with an introduction from without boats, a Senior Research Engineer at Mozilla working full-time on Rust: “I'm my name is without boats as in yeah see about this you can most people call me boats which is actually even more confusing I am researcher at Mozilla I work on rust my full-time job it's pretty cool I think.” Boats expresses deep appreciation to the organizers of RustLatam 2019, pointing out that this conference marks a major milestone: it is believed to be the very first Rust conference held outside of the United States or Europe. Boats highlights how thrilling it is to see the global Rust community thriving, expanding, and launching new events across the world before transitioning into the technical material.
The focus of the talk is `async`/`await`, a language feature boats has been working on directly for about a year and a half, building upon efforts that community members have pursued since the earliest pre-1.0 days of Rust. Slated to ship within the upcoming months of 2019, `async`/`await` represents arguably the biggest addition to Rust that year, offering a long-sought zero-cost abstraction for asynchronous I/O.
[01:55](https://www.youtube.com/watch?v=skos4B5x7qE&t=115s) The Mechanics of `async`/`await`
Boats explains that at a language level, the feature introduces two keywords: `async` and `await`. Marking a function with the `async` modifier fundamentally alters its behavior. Rather than executing sequentially to completion and returning its value immediately when called, an async function returns immediately with a `Future` that will eventually resolve to the declared return value. Inside an async function, developers can use the `await` operator on other futures. Applying `await` pauses execution of the current function until the awaited future becomes ready, making concurrent, non-blocking workflows far simpler to write than manual state machines or callback chains.
To illustrate, boats presents a toy example of an ORM-style database query helper:
```rust async fn get_user(username: &str) -> User { let query_str = format!("SELECT * FROM users WHERE name = '{}'", username); let response = db.query(&query_str).await; User::parse(response) } ```
In this pattern, calling `db.query(...)` initiates asynchronous I/O and yields a future. Adding `.await` suspends execution until the response arrives, at which point the domain object `User` is parsed and returned. Boats notes that the only visible difference between this and traditional blocking code is the addition of the `async` qualifier and the `.await` expressions:
- `async` modifies function signatures to return a `Future` instead of computing the result synchronously. - `await` suspends execution within an async function until the target future completes. - The ergonomics closely mimic blocking, sequential code, eliminating callback spaghetti without giving up non-blocking performance.
[04:26](https://www.youtube.com/watch?v=skos4B5x7qE&t=266s) The Philosophy of Zero-Cost Abstractions
The central achievement of Rust's async/await and futures model is that it qualifies as a genuine zero-cost abstraction. Boats emphasizes that this philosophy is Rust's defining trait and sets it apart from the majority of programming languages. While the concept did not originate with Rust, boats invokes Bjarne Stroustrup's canonical definition from C++ to explain the dual requirements of the principle:
> “a zero cost attraction means that when you don't use it you don't pay for it and further when you do use it you couldn't hand coded any better” — without boats
Boats unpacks both rules that every zero-cost abstraction must satisfy:
1. **No hidden global overhead:** A feature cannot impose performance or memory penalties on code paths or users who do not utilize it; it must not burden the entire language or slow down unrelated programs. 2. **Optimal handwritten parity:** When an engineer chooses to use the abstraction, it must compile down to machine code so efficient that writing it by hand with lower-level primitives would yield no meaningful performance gain. If developers feel compelled to write tedious low-level code by hand just to avoid a slow convenience feature, the abstraction has failed.
[05:59](https://www.youtube.com/watch?v=skos4B5x7qE&t=359s) The Dilemma of Blocking vs. Asynchronous I/O
Boats moves to the specific technical challenge: I/O scalability. Standard operating system I/O primitives are blocking. When a thread issues a read or write, the OS puts the thread to sleep, switching execution contexts until data is ready. While simple to reason about, blocking I/O fails to scale under high-concurrency network workloads where a service must maintain tens of thousands or millions of concurrent client connections.
Because Rust targets systems programming where computing resources and low-level control are paramount, having a competitive networking story is mandatory: “and so for us to really be successful in the network space we really need some sort of solution to this asynchronous i/o problem”. However, non-blocking operating system calls invert control: they return immediately, placing the responsibility squarely on the user's program to manage event loops, track state, and schedule callbacks when sockets become readable or writable. Managing that bookkeeping manually adds immense application complexity.
```mermaid flowchart TD accTitle: Blocking vs Non-Blocking IO Scalability accDescr: Comparison of operating system thread scheduling under blocking IO versus event-driven resumption under async IO.
subgraph Blocking["Blocking I/O"] B1["Thread issues I/O syscall"] --> B2["OS puts thread to sleep"] B2 --> B3["Heavy kernel context switch"] B3 --> B4["Fails at high concurrency"] end
subgraph Async["Asynchronous I/O"] A1["Syscall returns immediately"] --> A2["User code continues other work"] A2 --> A3["Program tracks readiness & callbacks"] A3 --> A4["Scales to high connection counts"] end ```
[07:36](https://www.youtube.com/watch?v=skos4B5x7qE&t=456s) The Rise and Fall of Rust's Green Threads
Early in its development, Rust addressed scalable I/O by implementing an M:N runtime based on "green threads," an architecture familiar from languages like Go (goroutines) or early Java. Under this model, application code still looks like regular blocking I/O: developers spawn threads and perform blocking calls, but these threads are lightweight userspace abstractions managed by a language runtime rather than native OS threads.
Green threads provide significant wins over native OS threads for high-volume network servers: - **Stack size and memory:** Native OS threads allocate fixed, multi-megabyte stacks upon creation. Green threads initialize with tiny stacks that grow dynamically, making it feasible to spawn hundreds of thousands of concurrent tasks without running out of memory. - **Context switching costs:** Switching between green threads takes place entirely in userspace memory within the application process, avoiding the heavy performance hit of kernel context switches.
Despite these benefits, Rust abandoned green threads shortly before its 1.0 release. The model fundamentally violated the first rule of zero-cost abstractions: it imposed costs on everyone, even those who never used green threads. Every Rust program was forced to carry a heavy runtime to manage the userspace scheduler.
This became an immediate barrier for one of Rust's primary adoption strategies: embedding Rust libraries into larger, existing C software architectures. Requiring a runtime environment to be initialized before calling any Rust function made incrementally replacing C components prohibitive. To keep Rust truly systems-level with a runtime footprint no heavier than C, green threads were excised from the language prior to 1.0.
[10:35](https://www.youtube.com/watch?v=skos4B5x7qE&t=635s) Futures and the Failure of Callback-Based Async
Rust's decision to remove green threads left native C-interoperability fast and cheap, preserving one of the language's core strengths. However, eliminating runtime-level green threads meant that asynchronous I/O still required a robust solution. The design constraint was clear: it could not be baked into a mandatory language runtime bundled with every compiled binary. Instead, Rust needed a purely library-based abstraction that programmers could opt into only when necessary.
Across the broader programming language landscape, the dominant paradigm for asynchronous values was the future—also known as a promise in JavaScript. A future represents an eventual value that may not have evaluated yet, allowing a caller to construct pipelines and combinators or use `async`/`await` syntax before the operation actually finishes. This pattern generalizes cleanly beyond network I/O to include timers or heavy CPU-bound tasks delegated to a thread pool.
```mermaid flowchart LR accTitle: Callback-based Future Allocation Problem accDescr: Diagram illustrating how scheduling multiple callbacks requires heap-allocating boxed trait objects for each step. subgraph CallbackChain ["Callback Model: Repeated Allocations"] direction LR A["Network Request Future"] -->|"schedule callback"| B["Box<dyn FnOnce>"] B -->|"schedule next step"| C["Box<dyn FnOnce>"] C -->|"schedule next step"| D["Box<dyn FnOnce>"] end ```
The standard implementation of futures in other languages relied on callbacks, where an asynchronous handle registers a closure to be executed whenever the underlying event resolves. In Rust, early experimentation demonstrated that this callback-driven approach broke down catastrophically. As Without Boats puts it:
> “This like approach failed the zero cost abstraction on the second principle where now you were it was not affecting people who work using it but if you did use it it would just be way slower than if you just had an end written something yourself and so why would you use it because if you wrote the thing yourself would be much faster”
- Callbacks demanded pervasive heap allocations, requiring each scheduled step to become a separate boxed trait object. - The proliferation of heap allocations introduced dynamic dispatch penalties everywhere. - The performance degradation violated Rust's zero-cost abstraction promise by running substantially slower than hand-crafted state machines.
[13:40](https://www.youtube.com/watch?v=skos4B5x7qE&t=820s) The Poll-Based Alternative: Alex Crichton and Aaron Turon's Model
Struggling through a slight cold, Without Boats credited Alex Crichton (who was present in the room) and Aaron Turon for designing a fundamentally different, poll-based future abstraction. Rather than having a future actively schedule a callback upon completion, the relationship is inverted: an external runtime component called an executor drives progress by repeatedly polling the future. When polled, the future either returns `Ready` with its resulting value or `Pending` if it cannot yet finish.
```mermaid flowchart TD accTitle: Rust Poll-Based Event Cycle accDescr: Cycle between Executor and Reactor during the lifecycle of a polled future until resolution. subgraph Lifecycle ["Poll-Based Async Cycle"] direction TD E["Executor"] -->|"1. poll(waker)"| F["Future State Machine"] F -->|"2. Pending (needs I/O)"| R["Reactor"] R -->|"3. I/O completes: wake()"| E F -.->|"4. Ready(T)"| D["Drop Handle & Complete"] end ```
This inverted control model introduced significant structural benefits:
- **Effortless Cancellation:** Canceling a task requires nothing more than abandoning the future. Because progress only happens during an active `poll` call, dropping the handle halts execution immediately, avoiding the messy cancellation tokens common to callback architectures. - **Decoupled Architecture:** Instead of hardwiring a global event loop, the poll model establishes clean boundaries separating the *executor* (which schedules tasks), the *reactor* (which registers system I/O events), and user application logic. Systems developers can swap either component independently based on specific performance demands. - **Single-Allocation State Machines:** The compiler flattens an asynchronous workflow into an `enum`-like state machine representation. Each pause point (such as waiting on an I/O event) forms a variant containing strictly the state variables required to resume execution from that point, overlaid across an internal union.
The state machine requires only a single heap allocation when spawned, containing the variant discriminant and the union of its possible execution states. This completely eliminated the chain of boxed callback allocations.
[16:52](https://www.youtube.com/watch?v=skos4B5x7qE&t=1012s) The Executor-Reactor Lifecycle and Early Benchmark Results
To clarify how the executor, reactor, and future interact in practice, Without Boats broke down the runtime sequence visually. When a task is initially spawned, it is allocated in a single heap location and its handle is registered with the executor. The executor polls the future, running native compiled code until an I/O operation is required. At that moment, the future delegates registration to the reactor, supplying a `Waker`.
The reactor monitors the operating system for the completion of the requested I/O event. When the event fires, the reactor invokes the `Waker`, passing notification back to the executor that the task is ready to make forward progress. The executor resumes polling, repeating this handoff loop until the future eventually yields a final ready value. Once resolved, the executor drops the handle and the allocated state machine is cleanly deallocated.
When this architecture was initially benchmarked against asynchronous implementations across multiple other programming languages, the Rust prototype achieved market-leading throughput. As Boats highlighted, Rust's implementation sat on the far-left edge of the benchmark chart, demonstrating that zero-cost abstractions could remain fully competitive with the fastest hand-tuned asynchronous runtimes available.
[18:43](https://www.youtube.com/watch?v=skos4B5x7qE&t=1123s) From Combinator Chains to the First Async/Await Syntax
Even though poll-based state machines yielded exceptional runtime efficiency, hand-writing nested `enum` state machines across an entire application was unworkable. To solve the ergonomics problem without discarding zero-cost guarantees, the Rust community initially introduced futures combinators, patterned directly after iterator adapters like `map`, `filter`, and `and_then`.
Under this style, an asynchronous operation fetching `rust-lang.org` and decoding the response into a string had to chain combinator closures together:
```rust // Conceptual pattern: Chaining combinators on a future fetch_url("https://www.rust-lang.org") .and_then(|response| response.into_string()) .map(|body| println!("{}", body)) ```
While combinators avoided raw state machine authoring, deeply nested chains of closures introduced syntactic noise and made complex control flow hard to read. To restore straight-line readability, the ecosystem began prototyping `async`/`await` syntax.
The earliest edition was delivered not as a built-in compiler feature, but as an experimental library utilizing procedural syntax macros. This macro transformed ordinary sequential-looking code back into a single unified future. Rather than scattering nested closures, the programmer wrote straight-line logic annotated with `async` and `.await`. Behind the scenes, each `await` point expanded directly into a polling loop: if the inner future yielded `Pending`, the outer wrapper immediately yielded `Pending` straight up the stack back to the top-level executor.
[21:09](https://www.youtube.com/watch?v=skos4B5x7qE&t=1269s) How `.await` Desugars and the Usability Crisis of Early Futures
When awaiting a future, the execution loops: if polling returns `Pending`, the task yields and waits until a waker notification signals it to resume. Once the future finishes, it yields its value, execution breaks out of the loop, and the await expression evaluates to that final result. While this async/await compilation strategy produced genuinely zero-cost futures, releasing the early `futures` crate into the wild exposed severe usability friction. Developers quickly encountered impenetrable compiler error messages complaining that futures were not `'static` or failed to implement particular traits. Programmers found themselves mindlessly following compiler suggestions—annotating closures with `move`, cloning data repeatedly, and wrapping variables in `Arc` or `RefCell`—adding unnecessary runtime overhead and turning their codebases into unreadable "soup."
```mermaid flowchart TD accTitle: Await Loop State Machine accDescr: The poll loop of an await expression handling pending and ready states. subgraph AwaitLoop["Await Expression Desugaring"] Start["Poll Awaited Future"] --> Check{"Result?"} Check -->|"Pending"| Wait["Wait for Wake-up Notification"] Wait -->|"Woken up"| Start Check -->|"Ready(val)"| Break["Break loop with value"] end ```
Combinators exacerbated the issue by generating sprawling, terminal-filling types, such as endless chains of `AndThen<AndThen<MapErr<TcpStream...>>>`, through which developers had to dig just to locate the underlying compiler error. Boats highlights a Reddit comment that captured the community's sheer exasperation: “having to use `RefCell` or `clone` everything for each future leads to over-complicated code and it makes me wish that Rust just had garbage collection.”
- Compiling async/await down to state machines was the right theoretical foundation, but early implementations alienated users. - Combinator chains created massive types that obscured the real source of compile errors across entire terminal screens. - Programmers reacted by sprinkling `move` closures, reference counters, and clones everywhere without understanding why.
[23:08](https://www.youtube.com/watch?v=skos4B5x7qE&t=1388s) The Borrowing Problem: Why Self-Referential Structs Break Rust
Evaluating the situation around 2017–2018, the Rust team identified two distinct problems: compiler diagnostics had to be improved by embedding async/await syntax directly into the language, and a fundamental design limitation dubbed "the borrowing problem" had to be solved. In the original design of `futures`, a program could not borrow across an await point. Whenever an await occurred, no references could remain active. Because normal Rust APIs idiomatically pass references, developers constantly bounced off this limitation. Boats explained the core design obstacle plainly: “In the original design of futures you could not borrow across an await point.”
```mermaid flowchart LR accTitle: Self-Referential Struct Invalidation on Move accDescr: How moving a self-referential struct invalidates internal pointers. subgraph MemoryLocationA["Stack Location A (Before Move)"] SQL_A["sql: String"] Ref_A["query_ref: &String"] -->|"points to"| SQL_A end
subgraph MemoryLocationB["Stack Location B (After Move)"] SQL_B["sql: String"] Ref_B["query_ref: &String"] end
MemoryLocationA -->|"memcpy / move"| MemoryLocationB Ref_B -.->|"dangling pointer!"| SQL_A ```
When an async function is compiled into a state machine struct that stores its local variables across suspension points, holding a reference across `.await` turns that struct into a self-referential data structure. For example, in a `get_user` method that builds a SQL string and passes a reference to `query(&sql).await`, the future's state contains both the string and a reference pointing directly back into another field of the very same struct. If Rust moves that struct in memory (via a standard bitwise copy), the self-referential pointer does not update; it still points to the old memory address, which has now been invalidated. This creates a dangling pointer, violating Rust’s core memory-safety guarantees.
- Rust has no general-purpose language mechanism to permit self-referential structs because standard moves bitwise copy memory without updating internal pointers. - Banning references across await points forced developers into awkward workarounds like reference counting and explicit clones. - If the compiler could safely permit cross-await borrows, async code could look and feel like standard, idiomatic Rust.
[26:17](https://www.youtube.com/watch?v=skos4B5x7qE&t=1577s) Inventing `Pin`: Zero-Cost Guarantees for Immovable Data
The key insight that resolved the borrowing problem was realizing that futures do not actually need to move. In the standard async executor model, the future is boxed on the heap; the reactor and executor pass lightweight handles back and forth, while the future itself remains completely stationary. Boats emphasized the realization that unlocked the solution: “The future itself never actually moves and so it's totally fine for the future to contain self-references as long as you never move it and you don't need to move it.”
To encode this immovable guarantee safely into the type system without runtime overhead, the team introduced `Pin`. `Pin` is a pointer adapter wrapping pointer types (such as `Pin<Box<T>>` or `Pin<&mut T>`) that guarantees the referent will never be moved again in memory until it is dropped and deallocated. Crucially, safe code is prevented from extracting an unpinned mutable reference from a `Pin`; doing so requires calling an `unsafe` method. The `Future` trait was rewritten so that its `poll` method accepts `Pin<&mut Self>` instead of a plain `&mut Self`. By guaranteeing that pinned futures cannot be relocated, compilers can safely generate self-referential async state machines.
```mermaid flowchart TD accTitle: Pinning in the Future API accDescr: Boxed future pinned on the heap and polled via pinned reference. subgraph Heap["Heap Memory"] PinnedFut["Pinned Future State Machine\n(Self-Referential Struct)"] end
subgraph Execution["Executor Polling"] BoxPin["Pin<Box<Future>>"] -->|"guarantees no moves"| PinnedFut PollCall["poll(self: Pin<&mut Self>, cx)"] -->|"passes pinned ref"| PinnedFut end ```
- `Pin` acts as a zero-cost wrapper around existing pointer types, enforcing that the underlying data will stay at a stable address. - Transitioning from `&mut self` to `Pin<&mut Self>` in `Future::poll` opened the door to natural borrowing patterns across `.await`. - Async functions with cross-yield borrows can now be written just like standard blocking I/O code, compiling into state machines as efficient as hand-written implementations.
[29:41](https://www.youtube.com/watch?v=skos4B5x7qE&t=1781s) Stabilization Timeline and Future Directions: Streams and Traits
With the design finalized, Boats laid out the concrete status and immediate roadmap for async Rust in early 2019. The foundation, `Pin`, had already reached stable Rust about a month prior. The core `Future` trait was slated for stabilization in Rust 1.35 or 1.36 within the following two to three months, paving the way to stabilize high-level `async`/`await` syntax by the end of summer 2019. This rollout would finally allow developers to build high-performance, non-blocking network services using familiar, linear syntax.
Looking beyond immediate stabilization, the team was already mapping out subsequent milestones. The next major primitive on the horizon was the `Stream` trait—the asynchronous counterpart to `Iterator`. While a `Future` resolves asynchronously to a single value, a `Stream` yields multiple values over time. This makes streams essential for protocols like WebSockets, streaming HTTP, and HTTP/2 server push, where applications exchange continuous series of requests and responses rather than single RPC round-trips. Boats also flagged another major language limitation being targeted: the inability to use `async fn` inside Rust traits.
- Pinning stabilized approximately one month before the talk (Rust 1.33). - The `Future` trait was projected to stabilize around Rust 1.35–1.36, with complete `async`/`await` syntax targeted for the end of summer 2019. - Long-term language work includes stabilizing `Stream` for multi-value asynchronous iteration and solving the challenges of `async fn` in traits.
[31:11](https://www.youtube.com/watch?v=skos4B5x7qE&t=1871s) Looking Beyond: Generators, Streams, and the Core Architectural Insights
There is substantial compiler work underway to fully support asynchronous programming, and looking beyond the immediate feature set reveals even broader possibilities for the language. Specifically, there is interest in bringing general generators to Rust, functioning much like generators do in Python or JavaScript. Rather than writing traditional functions that solely return values, developers would be able to write functions that yield. These functions could then be resumed right after the yield point, providing an ergonomic mechanism for constructing iterators and asynchronous streams—directly mirroring how an `async fn` allows a developer to write code that compiles into a `Future`.
Boats brings the talk to a close by summarizing the fundamental breakthroughs that made this entire zero-cost asynchronous model viable. As Boats puts it: “Recapping the real critical insights that led to this sort of zero cost async I/O model were that first was this poll based version of futures where we hope to compile these futures into these really tight state machines and then secondly that this way of doing async await syntax where we're able to have references across await points because of pinning.” Closing out their slide presentation, Boats encounters a minor slide glitch, remarking with a laugh that the final screen was “supposed to say thank you I don't know how it became an ex,” before exiting the stage to audience applause.
```mermaid flowchart TD accTitle: Pillars of Zero-Cost Async IO in Rust accDescr: The two core architectural innovations enabling zero-cost async in Rust: poll-based futures and pinning.
subgraph Insights["Core Architectural Innovations"] A["Poll-Based Futures"] --> B["State Machines<br/>(Tight, stack-allocated, no heap overhead)"] C["Pinning System (Pin)"] --> D["Borrowing Across Await Points<br/>(Guarantees memory immovable in place)"] end
subgraph FutureLang["Language Extensions"] E["Generators (Yield / Resume)"] --> F["Ergonomic Iterators & Streams"] end
Insights --> G["Zero-Cost Async I/O Model"] ```
- **Generators in Rust**: Intended to work similarly to Python or JavaScript generators by yielding values and resuming execution, offering a natural abstraction to implement streams and iterators just as async functions implement futures. - **Poll-based Future Design**: Compiles asynchronous tasks into compact, highly optimized state machines without requiring heap allocation or callback indirection per combinator. - **Pinning Guarantee**: Ensures self-referential structures generated across await points cannot be moved in memory, unlocking safe references across asynchronous suspension points.
## Notable quotes > [26:17](https://www.youtube.com/watch?v=skos4B5x7qE&t=1577s) The future itself never actually moves and so it's totally fine for the future to contain self-references as long as you never move it and you don't need to move it. — without boats
## Mentioned without boats, Alex Crichton, Aaron Turon, Bjarne Stroustrup, Rust, Mozilla Research, RustLatam 2019, C++, C, Go, Java, JavaScript, Python, Pin, Future, Stream
## Video description Boats is a member of the language design, library, and cargo teams of the Rust Project. They are Senior Research Engineer working on Rust for Mozilla Research. Contributing to Rust since 2014, they've done design work for significant language extensions like generic associated types and constant generics. For the last year, Boats has been focused on adding async/await syntax for non-blocking I/O to Rust.
Follow us on Twitter: https://twitter.com/rustlatamconf
[Source](https://www.youtube.com/watch?v=skos4B5x7qE)