O que faz e quando usar
Send e Sync são marker traits (traits marcadoras) que o compilador usa para garantir segurança de memória entre threads em tempo de compilação. Elas não têm métodos — apenas sinalizam ao compilador que um tipo pode ser usado de forma segura em contextos concorrentes.
Send: "Posso ser MOVIDO para outra thread"
┌──────────────────────────────────────────────┐
│ Thread A ──── move T ────> Thread B │
│ (T: Send) │
└──────────────────────────────────────────────┘
Sync: "Minha referência &T pode ser COMPARTILHADA entre threads"
┌──────────────────────────────────────────────┐
│ Thread A ─── &T ───┐ │
│ ├──> ambas lêem &T │
│ Thread B ─── &T ───┘ │
│ (T: Sync, ou seja, &T: Send) │
└──────────────────────────────────────────────┘
A relação fundamental: T é Sync se e somente se &T é Send.
Você raramente precisa implementar essas traits manualmente — elas são auto traits: o compilador implementa automaticamente para qualquer tipo cujos campos são todos Send/Sync.
Tipos e Funções Principais
Regras de derivação automática
| Se todos os campos são… | O tipo é… |
|---|---|
Send | Automaticamente Send |
Sync | Automaticamente Sync |
Algum campo é !Send | O tipo é !Send |
Algum campo é !Sync | O tipo é !Sync |
Tipos comuns e suas propriedades
| Tipo | Send | Sync | Por quê |
|---|---|---|---|
i32, f64, bool, char | Sim | Sim | Tipos primitivos são seguros |
String, Vec<T> | Se T: Send | Se T: Sync | Dados no heap, owned |
Arc<T> | Se T: Send+Sync | Se T: Send+Sync | Contagem atômica |
Mutex<T> | Se T: Send | Se T: Send | Lock garante acesso exclusivo |
RwLock<T> | Se T: Send | Se T: Send+Sync | Múltiplos leitores |
Rc<T> | Nao | Nao | Contagem não-atômica |
Cell<T> | Se T: Send | Nao | Mutação sem sincronização |
RefCell<T> | Se T: Send | Nao | Borrow checking não-atômico |
*const T, *mut T | Nao | Nao | Ponteiros brutos |
MutexGuard<T> | Nao | Se T: Sync | Não pode ser dropado em outra thread |
Exemplos de Código
Send em ação — thread::spawn exige Send
use std::thread;
// String é Send, então pode ser movida para outra thread
fn exemplo_send() {
let texto = String::from("Olá do Rust!");
let handle = thread::spawn(move || {
println!("{}", texto); // texto foi movido (Send)
});
handle.join().unwrap();
}
// Vec<i32> é Send (pois i32 é Send)
fn exemplo_send_vec() {
let dados = vec![1, 2, 3, 4, 5];
let handle = thread::spawn(move || {
let soma: i32 = dados.iter().sum();
println!("Soma: {}", soma);
});
handle.join().unwrap();
}
fn main() {
exemplo_send();
exemplo_send_vec();
}
!Send — Rc não pode ser enviado entre threads
use std::rc::Rc;
fn main() {
let dados = Rc::new(vec![1, 2, 3]);
// ERRO DE COMPILAÇÃO:
// `Rc<Vec<i32>>` cannot be sent between threads safely
// the trait `Send` is not implemented for `Rc<Vec<i32>>`
// std::thread::spawn(move || {
// println!("{:?}", dados);
// });
// Solução: usar Arc em vez de Rc
use std::sync::Arc;
let dados = Arc::new(vec![1, 2, 3]);
let dados_clone = Arc::clone(&dados);
std::thread::spawn(move || {
println!("{:?}", dados_clone); // Arc é Send+Sync
}).join().unwrap();
}
Sync em ação — compartilhando referências
use std::sync::Arc;
use std::thread;
fn main() {
// i32 é Sync, então &i32 pode ser compartilhado entre threads
let valor = 42;
thread::scope(|s| {
s.spawn(|| println!("Thread 1: {}", &valor));
s.spawn(|| println!("Thread 2: {}", &valor));
s.spawn(|| println!("Thread 3: {}", &valor));
});
// Arc<T> é Sync quando T: Send+Sync
let dados = Arc::new(vec![1, 2, 3, 4, 5]);
let mut handles = vec![];
for id in 0..3 {
let dados = Arc::clone(&dados);
handles.push(thread::spawn(move || {
// Múltiplas threads lendo &Vec<i32> simultaneamente
println!("Thread {}: soma = {}", id, dados.iter().sum::<i32>());
}));
}
for h in handles {
h.join().unwrap();
}
}
!Sync — Cell e RefCell
use std::cell::RefCell;
use std::sync::Arc;
fn main() {
// RefCell NÃO é Sync — não pode ser compartilhada entre threads via &
let dados = RefCell::new(42);
// ERRO: `RefCell<i32>` cannot be shared between threads safely
// thread::scope(|s| {
// s.spawn(|| {
// *dados.borrow_mut() += 1; // RefCell não é Sync!
// });
// });
// Solução: usar Mutex em vez de RefCell para threads
use std::sync::Mutex;
let dados = Arc::new(Mutex::new(42));
let d = Arc::clone(&dados);
std::thread::spawn(move || {
*d.lock().unwrap() += 1;
}).join().unwrap();
println!("Valor: {}", *dados.lock().unwrap());
}
Struct com campo !Send torna a struct !Send
use std::rc::Rc;
// Esta struct NÃO é Send porque contém Rc (que é !Send)
struct MeuTipo {
nome: String, // Send + Sync
dados: Vec<i32>, // Send + Sync
ref_count: Rc<i32>, // !Send + !Sync --> contamina toda a struct
}
fn main() {
let valor = MeuTipo {
nome: String::from("teste"),
dados: vec![1, 2, 3],
ref_count: Rc::new(42),
};
// ERRO: `Rc<i32>` cannot be sent between threads safely
// std::thread::spawn(move || {
// println!("{}", valor.nome);
// });
// Solução: substituir Rc por Arc
use std::sync::Arc;
struct MeuTipoThreadSafe {
nome: String,
dados: Vec<i32>,
ref_count: Arc<i32>,
}
let valor = MeuTipoThreadSafe {
nome: String::from("teste"),
dados: vec![1, 2, 3],
ref_count: Arc::new(42),
};
std::thread::spawn(move || {
println!("{}", valor.nome); // Agora funciona!
}).join().unwrap();
}
unsafe impl Send/Sync
Em casos raros, quando você sabe que um tipo é thread-safe mas o compilador não consegue provar, pode implementar Send/Sync manualmente com unsafe:
use std::thread;
// Wrapper em torno de um ponteiro bruto
struct MeuWrapper {
ptr: *mut i32,
len: usize,
}
// Ponteiros brutos são !Send e !Sync por padrão
// Se garantimos que o uso é seguro, podemos implementar manualmente
unsafe impl Send for MeuWrapper {}
unsafe impl Sync for MeuWrapper {}
impl MeuWrapper {
fn new(dados: &mut [i32]) -> Self {
MeuWrapper {
ptr: dados.as_mut_ptr(),
len: dados.len(),
}
}
// CUIDADO: chamar isso de forma insegura pode causar UB
unsafe fn get(&self, index: usize) -> i32 {
assert!(index < self.len);
*self.ptr.add(index)
}
}
fn main() {
let mut dados = vec![10, 20, 30, 40, 50];
let wrapper = MeuWrapper::new(&mut dados);
// Agora pode ser enviado para outra thread
let handle = thread::spawn(move || {
// CUIDADO: wrapper.ptr aponta para dados que podem ter sido dropados!
// Este exemplo é didático — em produção, use abstrações seguras
unsafe {
println!("Valor: {}", wrapper.get(0));
}
});
// PERIGO: dados pode ser modificado/dropado antes da thread acessar!
// Em código real, use scoped threads ou Arc<[i32]>
handle.join().unwrap();
}
Aviso: implementar Send/Sync com unsafe é uma das operações mais perigosas em Rust. Você está prometendo ao compilador que o tipo é seguro para uso concorrente. Se essa promessa for quebrada, o resultado é comportamento indefinido (data races, crashes, corrupção de memória).
Erro “future is not Send”
Um erro comum em programação async:
// Este padrão causa erro com Tokio:
//
// async fn processar() {
// let dados = Rc::new(42); // Rc não é Send!
// alguma_coisa_async().await; // .await pode mover para outra thread
// println!("{}", dados);
// }
//
// ERRO: future cannot be sent between threads safely
// `Rc<i32>` cannot be sent between threads safely
// Solução: usar Arc em vez de Rc
async fn processar() {
let dados = std::sync::Arc::new(42); // Arc é Send
// alguma_coisa_async().await;
println!("{}", dados);
}
fn main() {
// Em contexto async com Tokio, futures precisam ser Send
// para serem executados no runtime multi-thread
processar();
}
Padrões Comuns e Anti-padrões
Verificando se um tipo é Send/Sync
fn assert_send<T: Send>() {}
fn assert_sync<T: Sync>() {}
fn main() {
// Verificação em tempo de compilação
assert_send::<String>();
assert_sync::<String>();
assert_send::<Vec<i32>>();
assert_sync::<Vec<i32>>();
assert_send::<std::sync::Arc<String>>();
assert_sync::<std::sync::Arc<String>>();
assert_send::<std::sync::Mutex<Vec<i32>>>();
assert_sync::<std::sync::Mutex<Vec<i32>>>();
// Estes NÃO compilam:
// assert_send::<std::rc::Rc<i32>>(); // Rc não é Send
// assert_sync::<std::cell::Cell<i32>>(); // Cell não é Sync
// assert_sync::<std::cell::RefCell<i32>>(); // RefCell não é Sync
println!("Todos os tipos verificados!");
}
Tabela de decisão: escolhendo o tipo certo
Preciso compartilhar dados entre threads?
│
├─ NÃO → Use Rc<T>, RefCell<T>, Cell<T>
│
└─ SIM → Os dados são mutáveis?
│
├─ NÃO (apenas leitura) → Arc<T>
│
└─ SIM → Qual tipo de dado?
│
├─ Valor simples (bool, int) → Arc<AtomicT>
│
├─ Muitas leituras, poucas escritas → Arc<RwLock<T>>
│
└─ Leituras e escritas frequentes → Arc<Mutex<T>>
Anti-padrão: usar unsafe impl desnecessariamente
// ERRADO: usar unsafe impl Send para contornar erros do compilador
// sem entender as consequências
// struct Perigo {
// dados: std::rc::Rc<Vec<i32>>,
// }
// unsafe impl Send for Perigo {} // DATA RACE!
// CORRETO: corrigir o tipo para ser genuinamente thread-safe
struct Seguro {
dados: std::sync::Arc<Vec<i32>>,
}
fn main() {
let s = Seguro { dados: std::sync::Arc::new(vec![1, 2, 3]) };
std::thread::spawn(move || {
println!("{:?}", s.dados);
}).join().unwrap();
}
Garantias de Thread Safety
- Send e Sync são auto traits: implementadas automaticamente pelo compilador.
- Se todos os campos de uma struct são Send, a struct é Send. Idem para Sync.
- Qualquer campo !Send contamina a struct inteira como !Send.
thread::spawnexigeF: Send + 'static— a closure e seus dados capturados devem ser Send.thread::scopeexigeF: Sendmas não exige'static.- Em async Rust (Tokio multi-thread), futures devem ser Send para serem executados em diferentes threads do runtime.
unsafe impl Send/Syncé responsabilidade do programador — o compilador confia na sua promessa.
Veja Também
- Cell e RefCell — tipos !Sync com mutabilidade interior
- Mutex e RwLock — wrappers que tornam tipos Sync
- Tipos Atômicos — tipos primitivos que são Send + Sync
- std::thread — onde Send é exigido
- Padrões de Thread Safety — escolhendo a abstração certa
- Erro: future is not Send — resolvendo erros em async
- Unsafe Rust — quando e como usar unsafe
- Documentação oficial:
std::marker::Send