Send e Sync: Segurança entre Threads

Guia completo de Send e Sync em Rust: auto traits, thread safety, tipos !Send e !Sync, unsafe impl Send/Sync e regras de concorrência segura.

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 é…
SendAutomaticamente Send
SyncAutomaticamente Sync
Algum campo é !SendO tipo é !Send
Algum campo é !SyncO tipo é !Sync

Tipos comuns e suas propriedades

TipoSendSyncPor quê
i32, f64, bool, charSimSimTipos primitivos são seguros
String, Vec<T>Se T: SendSe T: SyncDados no heap, owned
Arc<T>Se T: Send+SyncSe T: Send+SyncContagem atômica
Mutex<T>Se T: SendSe T: SendLock garante acesso exclusivo
RwLock<T>Se T: SendSe T: Send+SyncMúltiplos leitores
Rc<T>NaoNaoContagem não-atômica
Cell<T>Se T: SendNaoMutação sem sincronização
RefCell<T>Se T: SendNaoBorrow checking não-atômico
*const T, *mut TNaoNaoPonteiros brutos
MutexGuard<T>NaoSe T: SyncNã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::spawn exige F: Send + 'static — a closure e seus dados capturados devem ser Send.
  • thread::scope exige F: Send mas 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