In this lesson, you will learn how to determine whether an async block can safely borrow local variables or if it must take ownership of them.
The Problem of Escaping Context
When you create an async block, Rust needs to know how long that block will live. Unlike a standard synchronous block that runs to completion immediately, an async block is a "future." It is a state machine that might be paused, resumed, or even moved to another thread to be executed by an executor.
If your async block captures a reference to a local variable (like a &String or a &i32), you create a problem: if the scope where that variable lives ends, the async block would be left with a "dangling reference"—a pointer to memory that no longer exists.
Moving vs. Borrowing
To solve this, Rust forces you to decide how the async block handles its context:
- Capturing by Borrowing: If you use an
asyncblock, it will attempt to borrow variables from the surrounding scope by default. If the compiler cannot guarantee that theasyncblock will finish before the variables are dropped, it will throw an error. This is common when you try to return anasyncblock from a function. - Capturing by Ownership (
move): By using theasync movekeyword, you force the block to take ownership of the variables it uses. This moves the data into the future's state machine, ensuring the data lives as long as the future itself.
Consider this example:
fn bad_async() -> impl std::future::Future<Output = String> {
let name = String::from("Rust");
// This fails because 'name' is dropped when the function returns
async { format!("Hello, {}", name) }
}
fn good_async() -> impl std::future::Future<Output = String> {
let name = String::from("Rust");
// This succeeds because 'move' forces ownership into the future
async move { format!("Hello, {}", name) }
}
By using async move, the lifetime of the name variable is effectively extended to match the lifetime of the returned future. If you didn't use move, the future would try to borrow name, which would die at the end of good_async, leading to a compile-time lifetime violation.
Key Takeaway: Use async move when you need your captured variables to outlive the scope in which the async block was created.