Send: Transferência Segura Entre Threads
Send é um trait que garante que um valor pode ser movido com segurança entre threads. Quando você implementa Send, você afirma: "este tipo é seguro para ser transferido para outra thread de execução". A maioria dos tipos em Rust implementa Send automaticamente, exceto aqueles que contêm referências não-seguras ou ponteiros brutos.
use std::thread;
#[derive(Clone)]
struct Mensagem {
conteudo: String,
}
impl Send for Mensagem {} // Implementação explícita (redundante aqui, só para demonstrar)
fn main() {
let msg = Mensagem {
conteudo: "Olá de outra thread".to_string(),
};
let handle = thread::spawn(move || {
println!("Recebido: {}", msg.conteudo);
});
handle.join().unwrap();
}
Tipos que não implementam Send incluem Rc<T> (referência compartilhada não-thread-safe) e Cell<T>. Tentar enviar esses para outra thread resultará em erro de compilação, protegendo você de data races em tempo de compilação, não em tempo de execução.
Sync: Compartilhamento Seguro Entre Threads
Sync garante que referências a um valor podem ser compartilhadas com segurança entre threads. Um tipo T é Sync se &T é Send. Isso significa que múltiplas threads podem acessar o mesmo valor simultaneamente sem corrupção de dados.
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let contador = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..5 {
let contador_clone = Arc::clone(&contador);
let handle = thread::spawn(move || {
let mut num = contador_clone.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Contador final: {}", *contador.lock().unwrap());
}
Arc<T> (Atomic Reference Counting) permite compartilhamento de propriedade entre threads, e Mutex<T> fornece exclusão mútua. Juntos, Arc<Mutex<T>> é simultaneamente Send e Sync, tornando-o ideal para estado compartilhado thread-safe.
O Impacto da Segurança em Tempo de Compilação
A verdadeira revolução de Rust é que estas garantias são verificadas antes de você executar uma única linha de código. Linguagens como Java e Python dependem de locks em tempo de execução e testes extensivos. Rust elimina classes inteiras de bugs antes da compilação.
// Este código NÃO compila:
use std::rc::Rc;
use std::thread;
fn main() {
let rc = Rc::new(5);
thread::spawn(move || {
println!("{}", rc);
});
// erro: `Rc<i32>` cannot be sent between threads safely
}
// Solução: usar Arc em vez de Rc
use std::sync::Arc;
fn main() {
let arc = Arc::new(5);
thread::spawn(move || {
println!("{}", arc);
}).join().unwrap();
}
O compilador força você a escolher as primitivas corretas desde o início. Não há surpresas em produção: se compila, é seguro. Essa abordagem previne deadlocks sutis, race conditions e memory safety issues que afetam sistemas concorrentes em outras linguagens.
Traits Automáticos e Casos Avançados
Implementação Automática
Rust implementa Send e Sync automaticamente para tipos que atendem aos critérios:
- Um struct é
Sendse todos seus campos sãoSend - Um struct é
Syncse todos seus campos sãoSync
struct Seguro {
valor: i32, // i32 é Send + Sync
texto: String, // String é Send + Sync
}
// Seguro é automaticamente Send + Sync
struct Inseguro {
ponteiro: *const u8, // *const não implementa Send
}
// Inseguro NÃO é Send/Sync (risco: dados não-sincronizados)
Casos Avançados: Negação Explícita
Ocasionalmente, você quer impedir que um tipo seja Send ou Sync, mesmo que todos os campos permitam:
use std::marker::PhantomData;
struct NaoEnviavel {
dados: String,
_phantom: PhantomData<*const ()>, // *const não é Send
}
// NaoEnviavel agora é !Send mesmo que String seja Send
Isso é útil quando seu tipo encapsula invariantes específicas de thread que não devem ser violadas.
Conclusão
Aprendemos que Send e Sync são os pilares da segurança de concorrência em Rust. Send garante movimentação segura entre threads, enquanto Sync permite compartilhamento seguro de referências. O brilho dessa abordagem é que essas garantias são verificadas em tempo de compilação, eliminando race conditions e deadlocks antes da execução. Use Arc<Mutex<T>> para estado compartilhado, respeite as restrições do compilador, e você construirá sistemas concorrentes robustos e eficientes.