Course contentsShow
← Rust
Lesson 1247 of 1,91011. Asynchronous ProgrammingFree lesson

The Send and Sync Bounds for Futures

Determine why Send requirements are critical for cross-thread executor support and how to enforce them.

In this lesson, you will learn why Send and Sync bounds are the bridge between your asynchronous tasks and the multi-threaded executors that run them.

Why Executors Need Send

In most modern Rust applications, we use multi-threaded executors (like Tokio’s default runtime). These executors use a work-stealing strategy: when one thread finishes its queue, it "steals" pending tasks from other threads.

For a task to be stolen and moved to a different thread, the Future representing that task must be Send. If a Future contains a non-Send type—such as Rc<T> or a RefCell<T>—the compiler will prevent you from spawning it onto a multi-threaded runtime. If it weren't for this check, moving a Future across threads could trigger a data race, as those types are explicitly designed for single-threaded usage.

The Impact of .await Points

When you use await, the compiler generates a state machine representing your Future. Crucially, this state machine captures every variable that is "live" across an await point.

Consider this example:

async fn process_data(data: Rc<String>) {
    // Some work...
    do_something().await; // The state machine is suspended here
    println!("{}", data);
}

Because Rc<String> is held across the await point, the entire Future state machine becomes !Send. Even if you aren't manually sending data, the compiler detects that the Future contains a type that cannot safely move between threads. To fix this, you would need to use Arc<T> instead of Rc<T>, which is specifically designed to be Send and Sync across thread boundaries.

Enforcing Bounds

If you are writing a library that accepts Futures, you can enforce these requirements in your bounds:

fn spawn_task<F>(future: F) 
where 
    F: Future<Output = ()> + Send + 'static 
{
    // ...
}

By requiring Send, you guarantee that your executor can safely migrate the task between its worker threads. By requiring 'static, you ensure the task doesn't rely on short-lived references, preventing dangling pointers when a task is moved to a background thread.

Key Takeaway: The Send bound on a Future is a safety guarantee that ensures the task’s captured state—held across await points—can be safely migrated between threads by the executor.