Course contentsShow
← Rust
Lesson 1147 of 1,91010. Concurrency PrimitivesFree lesson

Handling Non-Send Types in Async Code

You will overcome the challenges of moving non-thread-safe data through asynchronous task yields.

In this lesson, you will learn how to maintain state across .await points when dealing with types that do not implement the Send trait.

The Async Send Requirement

When you use a multi-threaded executor (like the default tokio runtime), the tasks you spawn must be Send. This is because the executor may start a task on one thread, hit an .await point where the task yields, and then resume that task on a completely different thread.

If your async block contains a local variable that is !Send (not thread-safe), the compiler will prevent that future from being Send. This often happens with types like Rc<T> or RefCell<T>, which are designed for single-threaded usage and explicitly lack the Send marker.

The Strategy: Scope and Ownership

If you find yourself with a !Send type, you have two primary ways to reconcile this with async tasks:

1. Constrain the Lifetime of !Send Data

The most straightforward approach is to ensure the !Send type never lives across an .await point. If the Rc<T> is only used synchronously before or after an asynchronous operation, the compiler will see that the !Send variable is dropped before the yield, allowing the remaining future to remain Send.

async fn process_data(data: Rc<String>) {
    // We can use the Rc here
    println!("Processing: {}", data);
    
    // Explicitly drop it before yielding
    drop(data);
    
    // Now this await is safe for a multi-threaded executor
    some_async_call().await;
}

2. Use Thread-Safe Alternatives

If you truly need to hold the state across an .await point, you must move away from single-threaded primitives. Replace Rc<T> with Arc<T> (Atomic Reference Counting) and RefCell<T> with Mutex<T> or RwLock<T>. These types implement Send and Sync (provided their inner data does), allowing them to safely migrate across thread boundaries during task yielding.

If you are writing a library, be careful not to leak !Send types into your public async API, as this forces your users to use single-threaded executors, limiting their ability to scale.

Key Takeaway: To keep a task Send, ensure any !Send data is dropped before an .await point or migrate that data to thread-safe equivalents like Arc and Mutex.