Rust
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Ownership and Borrowing
-
Section 4: Structuring Data
-
Section 5: Collections and Error Handling
-
Section 6: Traits and Generics
-
Section 7: Concurrency
-
Section 8: Building for the Web
-
Section 9: Memory and Performance
-
Section 10: More Standard Library and Ecosystem
-
Section 11: Advanced Rust
-
Section 12: Rust for Systems and WebAssembly
-
Section 13: Tooling and Best Practices
-
Section 14: Data Structures and Algorithms in Rust
-
Section 15: Practical Projects
-
Section 16: Interview Practice
-
Section 17: std::collections In Depth
-
Section 18: std::io and std::fs In Depth
-
Section 19: std::net
-
Section 20: std::option and std::result In Depth
-
Section 21: std::iter In Depth
-
Section 22: std::sync In Depth
-
Section 23: std::string and std::str
-
Section 24: Cargo and Crates.io Ecosystem
-
Section 25: Popular Crates Ecosystem
-
Section 26: Rust Memory Model Deep Dive
-
Section 27: More Practice Exercises
-
Section 28: More Interview Practice
-
Section 29: Async Rust Deep Dive
-
Section 30: Tokio Ecosystem In Depth
-
Section 31: Error Handling Ecosystem Deep Dive
-
Section 32: Serde In Depth
-
Section 33: Web Development with Rust Deep Dive
-
Section 34: Database Access Ecosystem
-
Section 35: Rust for Embedded Systems Deep Dive
-
Section 36: Rust Macros In Depth
-
Section 37: Advanced Trait System
-
Section 38: Unsafe Rust In Depth
-
Section 39: Rust CLI Development
-
Section 40: Testing Ecosystem Deep Dive
-
Section 41: WebAssembly Deep Dive
-
Section 42: Rust Design Patterns
-
Section 43: More Data Structures in Rust
-
Section 44: Final Practice Projects
-
Section 45: Rust Performance Optimization
-
Section 46: Rust Ecosystem Tooling
-
Section 47: More Interview and Review
146: The Future Trait Explained
Imagine you're at one of those fast-casual restaurants where you order at a counter, and instead of them calling your name, they hand you a little plastic buzzer. That buzzer is essentially a Future.
Now, think about how that buzzer actually works. It doesn't contain your burger; it's just a placeholder. It doesn't even "do" anything while it's sitting on the table. The kitchen is doing the work, and the buzzer is just the mechanism that tells you when the work is done. You don't stand at the counter asking "Is it ready?" every five seconds—that would be a waste of your time and the cashier's. Instead, you go sit down, relax, and wait for the buzzer to vibrate. When it does, you go back to the counter, trade the buzzer for your food, and finally have your meal.
Mapping the Buzzer to the Trait
In Rust, the Future trait is that plastic buzzer. When you mark a function as async, Rust doesn't actually execute the code inside immediately. Instead, it wraps that code into a state machine that implements the Future trait. Let's map the restaurant experience directly to the technical mechanics:
- The Buzzer: This is the
Futureobject. It represents a value that will eventually be available. - Checking the Food: This is the
poll()method. The runtime (the executor) callspollto see if the future has finished. - "Not ready yet": This is
Poll::Pending. When the executor polls the future and the work isn't done, it returns this value. - "Here is your burger": This is
Poll::Ready(val). The computation is complete, and the result is returned. - The Vibration: This is the
Waker. This is the most critical part. The future doesn't just sit there; it has aWakerthat tells the executor, "Hey, I'm actually ready now, please poll me again!"
The Laziness of Rust's Async
I want to highlight something that trips up a lot of people coming from JavaScript or C#. In those languages, a Promise or Task starts running the moment it's created. Rust is different. Rust futures are lazy.
If you call an async function in Rust, nothing happens. Literally nothing. You've just created a state machine that is sitting at its first step. It won't move a single inch until something polls it. This is why you can't just call an async function and expect it to run in the background; you need an executor (like Tokio or async-std) to actually drive the future to completion.
Looking Under the Hood
While we usually use async/await, it's helpful to see what the trait actually looks like. I've written a simplified version of a timer future here to show you how poll and Waker interact. I'm skipping the complex reactor logic for brevity, but pay attention to the return types.
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::{Duration, Instant};
struct SimpleTimer {
deadline: Instant,
}
impl Future for SimpleTimer {
type Output = String;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if Instant::now() >= self.deadline {
// The burger is ready!
Poll::Ready("Timer Finished!".to_string())
} else {
// Not ready. We tell the waker to wake us up later.
// In a real scenario, the OS or a timer wheel would handle this.
cx.waker().wake_by_ref();
Poll::Pending
}
}
}
In the example above, the poll method is the heart of the operation. Notice the Pin wrapper? I know we'll dive deeper into pinning later, but for now, just know that because futures often contain references to their own data (self-referential structs), they can't be moved in memory once they start being polled. Pinning "pins" them in place so those references don't become invalid.
Where the Executor Fits In
If the Future is the buzzer, the Executor is the entire restaurant system. The executor maintains a list of all the futures that are currently "pending." It doesn't loop through them constantly (which would kill your CPU); it sleeps until a Waker signals that a specific future is ready to be polled again. This efficiency is why Rust can handle tens of thousands of concurrent connections on a single thread without breaking a sweat.
📋 Practical Task
Implementing a Manual Delayed-Value Future
Your task is to create a custom Future called DelayedValue. Instead of using async { ... } blocks, you must implement the Future trait manually for a struct.
Requirements:
- Create a struct
DelayedValuethat holds acount(integer) and atarget(integer). - Implement
FutureforDelayedValue. - The
Outputtype should beString. - In the
pollmethod:- If
countis less thantarget, incrementcount, callcx.waker().wake_by_ref(), and returnPoll::Pending. - If
counthas reachedtarget, returnPoll::Readywith a string saying "Reached target!".
- If
Hint: You will need to import std::pin::Pin, std::task::{Context, Poll}, and std::future::Future. You can test this by using futures::executor::block_on or a similar executor to run your future to completion.
There are no comments for now.