← Back to Rust Course | Chapter 14: Closures, Iterators & Async | Lesson 5 of 6

Async/Await Basics

async and .await let a program pause one task while waiting for something slow, and get on with other work in the meantime.

Declaring an Async Function

An async fn looks like a normal function but returns a Future representing its eventual result, rather than running its body immediately when called. Async syntax needs the 2018+ Rust edition and a runtime to actually execute, so this snippet shows the shape as a comment and demonstrates the underlying idea (a value that isn't ready immediately) with plain std code.

Example: Declaring an Async Function

markup
// Async syntax (needs edition 2018+ and a runtime), shown conceptually:
// async fn compute() -> i32 {
//     42
// }

fn main() {
    println!("async fn compute() would return a Future, not an i32 directly");
    println!("It must be awaited inside an async runtime to actually run");
}

The Role of .await

Inside an async context, .await is used on a Future to drive it to completion, pausing that task (without blocking the whole program) until the value is ready. Since .await requires edition 2018+ and a runtime, this snippet keeps the async shape as a comment and shows the conceptual chaining with plain function calls instead.

Example: The Role of .await

markup
// Conceptual async chain (needs edition 2018+ and a runtime):
// async fn get_value() -> i32 { 10 }
// async fn use_value() -> i32 {
//     let v = get_value().await;
//     v + 1
// }

fn get_value() -> i32 {
    10
}

fn use_value() -> i32 {
    let v = get_value();
    v + 1
}

fn main() {
    println!("Synchronous stand-in result: {}", use_value());
    println!("An async version would use .await instead of a direct call");
}

Why Async Needs a Runtime

The Rust standard library defines the Future trait and async/.await syntax, but does not include an executor to actually run futures -- that job belongs to a runtime crate such as Tokio or async-std.

Example: Why Async Needs a Runtime

markup
fn main() {
    println!("The standard library defines Future and async/.await syntax");
    println!("But executing futures requires an external runtime crate");
    println!("Common choices: tokio, async-std");
}

Async Is for Concurrency, Not Parallelism

Async/await shines when a program is waiting on many slow I/O operations (like network requests) at once, letting a single thread juggle them efficiently, rather than for speeding up raw CPU-bound computation.

Example: Async Is for Concurrency, Not Parallelism

markup
fn main() {
    println!("Async excels at juggling many waiting I/O tasks concurrently");
    println!("For CPU-bound parallel work, OS threads are usually the better tool");
}
Common Mistakes
  1. Assuming an async fn runs immediately when called -- it actually returns a lazy Future that does nothing until it is awaited or polled.
  2. Trying to run async code without an async runtime (like Tokio or async-std) actually driving the futures to completion.
  3. Forgetting .await can only be used inside an async fn or async block, not in ordinary synchronous code.
Chapter Summary
  • An async fn returns a Future immediately, representing work that hasn't started yet.
  • .await drives a Future toward completion, yielding control back to the runtime while it waits.
  • Async code needs a runtime (like Tokio) to actually execute the futures it produces -- the standard library alone doesn't run them.
  • Async/await is designed for efficiently waiting on many I/O-bound tasks concurrently, not for CPU-bound parallelism.
🔒

Chapter Quiz — Complete all 6 topics to unlock

0/6 topics done

Complete these topics first:

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.