Challenge 2: Three Concurrent Tasks — Possible Solution ==================================================================== use std::time::Duration; #[tokio::main] async fn main() { let mut handles = vec![]; for i in 0..3 { handles.push(tokio::spawn(async move { tokio::time::sleep(Duration::from_millis(100)).await; println!("Task {} finished", i); })); } for handle in handles { handle.await.unwrap(); } println!("All tasks completed"); } WHY THIS WORKS AS AN ANSWER ------------------------------ Each iteration of the loop calls tokio::spawn(async move { ... }), producing a lightweight async task rather than a full OS thread — exactly this chapter's distinction from Course 2's thread::spawn: this pattern would remain cheap even spawning thousands of these, not just 3. Each task's own async block simulates work with tokio::time::sleep (an ASYNC sleep, not std::thread::sleep — using the blocking std version here would defeat the entire purpose by tying up the underlying OS thread instead of yielding it back to other tasks) and then prints its own task number, using move to give each task its own owned copy of the loop variable i (the same move-closure pattern Course 2 Chapter 5 used for thread::spawn, now applied to an async block). Because all 3 tokio::spawn calls happen BEFORE any handle is awaited, the three tasks' sleeps run CONCURRENTLY rather than one after another — awaiting handle 0, then handle 1, then handle 2 in the second loop simply waits for each task's own already-in-progress work to finish, not starting them sequentially. The final println! only runs once every handle.await has completed, guaranteeing "All tasks completed" prints last, after all three task-completion messages.