Future is Not Send
O erro “future is not Send” é um dos mais confusos da programação assíncrona em Rust. Ele ocorre quando você tenta enviar um future para outra thread (como ao usar tokio::spawn), mas o future contém tipos que não são seguros para compartilhar entre threads.
A Mensagem de Erro
error: future cannot be sent between threads safely
--> src/main.rs:10:5
|
10 | tokio::spawn(async {
| ^^^^^^^^^^^^ future created by async block is not `Send`
|
= help: within `{async block}`, the trait `Send` is not implemented for `Rc<String>`
note: future is not `Send` as this value is used across an await
--> src/main.rs:12:9
|
11 | let dados = Rc::new(String::from("olá"));
| ----- has type `Rc<String>` which is not `Send`
12 | alguma_funcao().await;
| ^^^^^^ await occurs here, with `dados` maybe used later
O Que Significa
No Rust, o trait Send indica que um tipo pode ser transferido com segurança entre threads. Quando você usa tokio::spawn (ou equivalentes em outros runtimes async), o future é movido para uma thread de trabalho. Para isso ser seguro, tudo dentro do future precisa ser Send.
Tipos que NÃO são Send:
| Tipo | Por que não é Send | Alternativa Send |
|---|---|---|
Rc<T> | Contagem de referência não-atômica | Arc<T> |
RefCell<T> | Empréstimo verificado em runtime, não thread-safe | Mutex<T> ou RwLock<T> |
Cell<T> | Mutabilidade interior não thread-safe | AtomicXxx ou Mutex<T> |
MutexGuard<T> | Lock guard mantido através de .await | Soltar guard antes do await |
Ponteiros raw *const T / *mut T | Sem garantias de segurança | Encapsular em tipo Send |
O problema não é apenas ter esses tipos, mas mantê-los vivos através de um .await. O compilador precisa garantir que, se o runtime pausar o future no .await e continuar em outra thread, todos os dados serão válidos.
Código com Erro
Rc através de await
use std::rc::Rc;
#[tokio::main]
async fn main() {
tokio::spawn(async {
let dados = Rc::new(String::from("compartilhado"));
fazer_algo().await; // ERRO: `Rc` vivo através do await
println!("{}", dados);
});
}
async fn fazer_algo() {
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
MutexGuard através de await
use std::sync::Mutex;
#[tokio::main]
async fn main() {
let dados = std::sync::Arc::new(Mutex::new(vec![1, 2, 3]));
tokio::spawn(async move {
let guard = dados.lock().unwrap();
// ERRO: MutexGuard mantido através do await
fazer_algo().await;
println!("{:?}", guard);
});
}
RefCell em contexto async
use std::cell::RefCell;
#[tokio::main]
async fn main() {
tokio::spawn(async {
let celula = RefCell::new(42);
fazer_algo().await; // ERRO
println!("{}", celula.borrow());
});
}
Como Resolver
Solução 1: Substituir Rc por Arc
use std::sync::Arc;
#[tokio::main]
async fn main() {
tokio::spawn(async {
let dados = Arc::new(String::from("compartilhado"));
fazer_algo().await;
println!("{}", dados); // Funciona! Arc é Send
});
}
async fn fazer_algo() {
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
Solução 2: Substituir RefCell por Mutex/RwLock
use std::sync::{Arc, Mutex};
#[tokio::main]
async fn main() {
let dados = Arc::new(Mutex::new(vec![1, 2, 3]));
let dados_clone = Arc::clone(&dados);
tokio::spawn(async move {
dados_clone.lock().unwrap().push(4);
fazer_algo().await;
println!("{:?}", dados_clone.lock().unwrap());
});
}
Para async, prefira tokio::sync::Mutex que não bloqueia a thread:
use std::sync::Arc;
use tokio::sync::Mutex;
#[tokio::main]
async fn main() {
let dados = Arc::new(Mutex::new(vec![1, 2, 3]));
let dados_clone = Arc::clone(&dados);
tokio::spawn(async move {
{
let mut guard = dados_clone.lock().await;
guard.push(4);
} // guard liberado antes do await
fazer_algo().await;
println!("{:?}", dados_clone.lock().await);
});
}
Solução 3: Soltar o Guard Antes do await
Se o tipo não-Send é temporário, garanta que ele não cruza o .await:
use std::sync::{Arc, Mutex};
#[tokio::main]
async fn main() {
let dados = Arc::new(Mutex::new(vec![1, 2, 3]));
let dados_clone = Arc::clone(&dados);
tokio::spawn(async move {
// Escopo limita o guard
{
let mut guard = dados_clone.lock().unwrap();
guard.push(4);
} // guard destruído aqui — antes do await
fazer_algo().await; // OK: nenhum tipo não-Send vivo
let guard = dados_clone.lock().unwrap();
println!("{:?}", *guard);
});
}
Solução 4: Usar spawn_local para Tasks Não-Send
Se você realmente precisa de tipos não-Send, use spawn_local (executa na mesma thread):
use std::rc::Rc;
use tokio::task;
#[tokio::main]
async fn main() {
let local = task::LocalSet::new();
local.run_until(async {
task::spawn_local(async {
let dados = Rc::new(String::from("local"));
fazer_algo().await;
println!("{}", dados); // OK com spawn_local
}).await.unwrap();
}).await;
}
Solução 5: Extrair a Parte Não-Send para uma Closure
use std::sync::Arc;
#[tokio::main]
async fn main() {
let dados = Arc::new(std::sync::Mutex::new(0));
let dados_clone = Arc::clone(&dados);
tokio::spawn(async move {
// Processa antes do await
let valor = {
let guard = dados_clone.lock().unwrap();
*guard // Copia o valor, guard destruído no fim do bloco
};
fazer_algo().await;
println!("Valor: {}", valor);
});
}
async fn fazer_algo() {
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
Regra de Ouro
Se o tipo cruza um .await e será usado por tokio::spawn:
Rc->ArcRefCell->tokio::sync::Mutexoutokio::sync::RwLockCell->std::sync::atomic::AtomicXxxMutexGuard-> solte antes do.await