Rust Admin

Send e Sync em Rust: Segurança de Concorrência em Tempo de Compilação na Prática Já leu

Send: Transferência Segura Entre Threads é um trait que garante que um valor pode ser movido com segurança entre threads. Quando você implementa , você afirma: "este tipo é seguro para ser transferido para outra thread de execução". A maioria dos tipos em Rust implementa automaticamente, exceto aqueles que contêm referências não-seguras ou ponteiros brutos. Tipos que não implementam incluem (referência compartilhada não-thread-safe) e . 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 garante que referências a um valor podem ser compartilhadas com segurança entre threads. Um tipo é se é . Isso significa que múltiplas threads podem acessar o mesmo valor simultaneamente sem corrupção de dados. (Atomic Reference Counting) permite compartilhamento de propriedade entre threads, e fornece exclusão mútua. Juntos, é simultaneamente e , tornando-o ideal para estado compartilhado thread-safe. O Impacto da Segurança em Tempo de

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 é Send se todos seus campos são Send
  • Um struct é Sync se todos seus campos são Sync
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.

Referências


Artigos relacionados