Skip to content

Commit 024a3d6

Browse files
committed
Fix async book issues
74: Add Pin<Box<Fut>> to remove requirement for Unpin. Box<T> has a blanket implementation for Unpin 75: Remove soft-deprecated pin_mut! and replace with std::pin::pin! 76: Replace near-duplicated chapter 3 example with something different
1 parent a0228eb commit 024a3d6

3 files changed

Lines changed: 59 additions & 33 deletions

File tree

‎async-book/src/ch03-how-poll-works.md‎

Lines changed: 48 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -133,53 +133,78 @@ impl Future for MyFuture {
133133
4. **Don't poll after `Ready`** — behavior is **unspecified** (may panic, return `Pending`, or repeat `Ready`). Only `FusedFuture` guarantees safe post-completion polling
134134

135135
<details>
136-
<summary><strong>🏋️ Exercise: Implement a CountdownFuture</strong> (click to expand)</summary>
136+
<summary><strong>🏋️ Exercise: Spurious-Wake-Safe Flag Future</strong> (click to expand)</summary>
137137

138-
**Challenge**: Implement a `CountdownFuture` that counts down from N to 0, *printing* the current count as a side-effect each time it's polled. When it reaches 0, it completes with `Ready("Liftoff!")`. (Note: a `Future` produces only **one** final value — the printing is a side-effect, not a yielded value. For multiple async values, see `Stream` in Ch. 11.)
138+
**Challenge**: Implement a `FlagFuture` that wraps a shared `Arc<AtomicBool>` flag. When polled, it checks whether the flag is `true`. If so, it completes with `Ready(())`. If not, it stores the waker and returns `Pending`. The twist: the future must handle **spurious wakes** correctly — it must re-check the flag on every poll, never assuming the flag is set just because it was woken.
139139

140-
*Hint*: This doesn't need a real I/O source — it can wake itself immediately with `cx.waker().wake_by_ref()` after each decrement.
140+
*Hint*: You'll need an `Arc<Mutex<Option<Waker>>>` (or similar) so an external thread can set the flag and wake the future. Use `poll_fn` for a concise alternative solution.
141141

142142
<details>
143143
<summary>🔑 Solution</summary>
144144

145145
```rust
146146
use std::future::Future;
147147
use std::pin::Pin;
148-
use std::task::{Context, Poll};
148+
use std::sync::{Arc, Mutex};
149+
use std::sync::atomic::{AtomicBool, Ordering};
150+
use std::task::{Context, Poll, Waker};
149151

150-
struct CountdownFuture {
151-
count: u32,
152+
struct FlagFuture {
153+
flag: Arc<AtomicBool>,
154+
waker_slot: Arc<Mutex<Option<Waker>>>,
152155
}
153156

154-
impl CountdownFuture {
155-
fn new(start: u32) -> Self {
156-
CountdownFuture { count: start }
157+
impl FlagFuture {
158+
fn new(flag: Arc<AtomicBool>, waker_slot: Arc<Mutex<Option<Waker>>>) -> Self {
159+
FlagFuture { flag, waker_slot }
157160
}
158161
}
159162

160-
impl Future for CountdownFuture {
161-
type Output = &'static str;
163+
impl Future for FlagFuture {
164+
type Output = ();
162165

163-
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
164-
if self.count == 0 {
165-
Poll::Ready("Liftoff!")
166+
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
167+
// Always re-check the actual condition — never trust the wake alone
168+
if self.flag.load(Ordering::Acquire) {
169+
return Poll::Ready(());
170+
}
171+
172+
// Store/update the waker so we get notified
173+
let mut slot = self.waker_slot.lock().unwrap();
174+
*slot = Some(cx.waker().clone());
175+
176+
// Re-check after storing the waker to avoid a race:
177+
// the flag could have been set between our first check
178+
// and storing the waker
179+
if self.flag.load(Ordering::Acquire) {
180+
Poll::Ready(())
166181
} else {
167-
println!("{}...", self.count);
168-
self.count -= 1;
169-
// Wake immediately — we're always ready to make progress
170-
cx.waker().wake_by_ref();
171182
Poll::Pending
172183
}
173184
}
174185
}
175186

176-
// Usage with our mini executor or tokio:
177-
// let msg = block_on(CountdownFuture::new(5));
178-
// prints: 5... 4... 3... 2... 1...
179-
// msg == "Liftoff!"
187+
// The setter side (e.g., another thread or task):
188+
fn set_flag(flag: &AtomicBool, waker_slot: &Mutex<Option<Waker>>) {
189+
flag.store(true, Ordering::Release);
190+
if let Some(waker) = waker_slot.lock().unwrap().take() {
191+
waker.wake();
192+
}
193+
}
194+
195+
// Equivalent using poll_fn:
196+
// async fn wait_for_flag(flag: Arc<AtomicBool>, waker_slot: Arc<Mutex<Option<Waker>>>) {
197+
// std::future::poll_fn(|cx| {
198+
// if flag.load(Ordering::Acquire) {
199+
// return Poll::Ready(());
200+
// }
201+
// *waker_slot.lock().unwrap() = Some(cx.waker().clone());
202+
// if flag.load(Ordering::Acquire) { Poll::Ready(()) } else { Poll::Pending }
203+
// }).await
204+
// }
180205
```
181206

182-
**Key takeaway**: Even though this future is always ready to progress, it returns `Pending` to yield control between steps. It calls `wake_by_ref()` immediately so the executor re-polls it right away. This is the basis of cooperative multitasking — each future voluntarily yields.
207+
**Key takeaway**: The double-check pattern (check → store waker → check again) is essential to avoid a race between the condition changing and the waker being registered. This is the real-world pattern that all I/O futures use internally, and it demonstrates why handling spurious wakes matters.
183208

184209
</details>
185210
</details>

‎async-book/src/ch04-pin-and-unpin.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ impl Unpin for MySimpleFuture {} // "I'm safe to move, trust me"
130130
| What | When | How |
131131
|------|------|-----|
132132
| Pin a future on the heap | Storing in a collection, returning from function | `Box::pin(future)` |
133-
| Pin a future on the stack | Local use in `select!` or manual polling | `tokio::pin!(future)` or `pin_mut!` from `pin-utils` |
133+
| Pin a future on the stack | Local use in `select!` or manual polling | `std::pin::pin!(future)` or `tokio::pin!(future)` |
134134
| Pin in function signature | Accepting pinned futures | `future: Pin<&mut F>` |
135135
| Require Unpin | When you need to move a future after creation | `F: Future + Unpin` |
136136

‎async-book/src/ch06-building-futures-by-hand.md‎

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -279,21 +279,21 @@ use std::task::{Context, Poll};
279279
pub struct RetryFuture<F, Fut, T, E>
280280
where
281281
F: Fn() -> Fut,
282-
Fut: Future<Output = Result<T, E>> + Unpin,
282+
Fut: Future<Output = Result<T, E>>,
283283
{
284284
factory: F,
285-
current: Option<Fut>,
285+
current: Option<Pin<Box<Fut>>>,
286286
remaining: usize,
287287
last_error: Option<E>,
288288
}
289289

290290
impl<F, Fut, T, E> RetryFuture<F, Fut, T, E>
291291
where
292292
F: Fn() -> Fut,
293-
Fut: Future<Output = Result<T, E>> + Unpin,
293+
Fut: Future<Output = Result<T, E>>,
294294
{
295295
pub fn new(max_attempts: usize, factory: F) -> Self {
296-
let current = Some((factory)());
296+
let current = Some(Box::pin((factory)()));
297297
RetryFuture {
298298
factory,
299299
current,
@@ -306,22 +306,23 @@ where
306306
impl<F, Fut, T, E> Future for RetryFuture<F, Fut, T, E>
307307
where
308308
F: Fn() -> Fut + Unpin,
309-
Fut: Future<Output = Result<T, E>> + Unpin,
310-
T: Unpin,
309+
Fut: Future<Output = Result<T, E>>,
311310
E: Unpin,
312311
{
313312
type Output = Result<T, E>;
314313

315314
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
315+
// Pin<Box<Fut>> is always Unpin, so the struct is Unpin when F and E are.
316+
// This lets us safely use get_mut() without any unsafe code.
316317
loop {
317318
if let Some(ref mut fut) = self.current {
318-
match Pin::new(fut).poll(cx) {
319+
match fut.as_mut().poll(cx) {
319320
Poll::Ready(Ok(val)) => return Poll::Ready(Ok(val)),
320321
Poll::Ready(Err(e)) => {
321322
self.last_error = Some(e);
322323
if self.remaining > 0 {
323324
self.remaining -= 1;
324-
self.current = Some((self.factory)());
325+
self.current = Some(Box::pin((self.factory)()));
325326
// Loop to poll the new future immediately
326327
} else {
327328
return Poll::Ready(Err(self.last_error.take().unwrap()));
@@ -342,7 +343,7 @@ where
342343
// }).await;
343344
```
344345

345-
**Key takeaway**: The retry future is itself a state machine: it holds the current attempt and creates new inner futures on failure. This is how combinators compose — futures all the way down.
346+
**Key takeaway**: The retry future is itself a state machine: it holds the current attempt and creates new inner futures on failure. Wrapping the inner future in `Pin<Box<Fut>>` removes the `Fut: Unpin` bound — since `Pin<Box<T>>` is always `Unpin`, the struct remains easy to work with while supporting any future type. This is how combinators compose — futures all the way down.
346347

347348
</details>
348349
</details>

0 commit comments

Comments
 (0)