Modulo std::marker em Rust: Traits de Marcacao

Guia completo do modulo std::marker em Rust: PhantomData, Sized, Unpin, Send, Sync, Copy e programacao em nivel de tipos com exemplos praticos.

Visao Geral do Modulo std::marker

O modulo std::marker contem traits de marcacao (marker traits) — traits que nao possuem metodos, mas comunicam propriedades de tipos ao compilador. Elas sao fundamentais para o sistema de tipos do Rust e controlam aspectos como:

  • Se um tipo pode ser copiado bit-a-bit (Copy)
  • Se um tipo pode ser enviado entre threads (Send)
  • Se um tipo pode ser compartilhado entre threads (Sync)
  • Se o tamanho do tipo e conhecido em tempo de compilacao (Sized)
  • Se um tipo pode ser movido de tras de um Pin (Unpin)

Alem das traits, o modulo fornece PhantomData<T>, um tipo de tamanho zero que permite “fingir” que uma struct usa um tipo generico sem realmente armazena-lo. Isso e essencial para programacao em nivel de tipos, controle de variancia e seguranca de FFI.


Traits e Tipos Principais

Traits de Marcacao

TraitAuto?Descricao
CopyNaoTipo pode ser copiado bit-a-bit (implica Clone)
SendSimTipo pode ser transferido entre threads
SyncSim&T pode ser compartilhado entre threads
SizedSimTamanho conhecido em tempo de compilacao
UnpinSimTipo pode ser movido de tras de Pin
UnsizeTipo pode ser “reduzido” (unstable)

“Auto” significa que o compilador implementa a trait automaticamente quando todos os campos a satisfazem.

PhantomData

TipoDescricao
PhantomData<T>Tipo de tamanho zero que “marca” que T e usado

Exemplos Praticos

1. Copy — Copia Implicita vs Move

// Tipos Copy: a atribuicao copia os bits, sem invalidar o original
#[derive(Debug, Clone, Copy)]
struct Ponto {
    x: f64,
    y: f64,
}

// Tipos nao-Copy: a atribuicao move o valor
#[derive(Debug, Clone)]
struct Dados {
    valores: Vec<i32>,
}

fn main() {
    // Copy: ambas as variaveis sao validas
    let p1 = Ponto { x: 1.0, y: 2.0 };
    let p2 = p1; // copia bit-a-bit
    println!("p1={p1:?}, p2={p2:?}"); // ambos validos!

    // Nao-Copy: move semantics
    let d1 = Dados { valores: vec![1, 2, 3] };
    let d2 = d1; // move! d1 nao pode mais ser usado
    // println!("{d1:?}"); // ERRO de compilacao!
    println!("d2={d2:?}");

    // Tipos primitivos sao Copy
    let a: i32 = 42;
    let b = a;
    println!("a={a}, b={b}"); // ambos validos

    // Tuplas de tipos Copy tambem sao Copy
    let t1 = (1, 2.0, true);
    let t2 = t1;
    println!("{t1:?} {t2:?}");
}

Regra: Um tipo pode ser Copy apenas se todos os seus campos forem Copy e ele nao implementar Drop. Tipos com alocacao heap (como String, Vec, Box) nunca sao Copy.

2. Send e Sync — Seguranca Entre Threads

use std::sync::{Arc, Mutex};
use std::thread;

// String e Send + Sync
// Rc<T> NAO e Send nem Sync
// Arc<T> e Send + Sync (quando T e Send + Sync)
// Mutex<T> e Send + Sync (quando T e Send)
// Cell<T> e Send mas NAO e Sync

fn demonstrar_send() {
    let dados = String::from("transferido entre threads");

    // String e Send, entao pode ser movida para outra thread
    let handle = thread::spawn(move || {
        println!("Recebi: {dados}");
    });

    handle.join().unwrap();
}

fn demonstrar_sync() {
    let dados = Arc::new(Mutex::new(vec![1, 2, 3]));

    let mut handles = vec![];

    for i in 0..3 {
        let dados = Arc::clone(&dados);
        // Arc<Mutex<Vec<i32>>> e Send + Sync,
        // entao pode ser compartilhado entre threads
        handles.push(thread::spawn(move || {
            let mut guard = dados.lock().unwrap();
            guard.push(i * 10);
        }));
    }

    for h in handles {
        h.join().unwrap();
    }

    println!("Dados finais: {:?}", dados.lock().unwrap());
}

fn main() {
    demonstrar_send();
    demonstrar_sync();
}

Quando implementar manualmente Send/Sync: Apenas quando voce tem um tipo com ponteiros raw e pode garantir a seguranca. A implementacao manual requer unsafe:

struct MeuBuffer {
    ptr: *mut u8,
    len: usize,
}

// SAFETY: MeuBuffer tem ownership exclusivo do ponteiro,
// entao e seguro envia-lo entre threads
unsafe impl Send for MeuBuffer {}

// SAFETY: acesso compartilhado e seguro porque
// nao ha mutacao sem sincronizacao
unsafe impl Sync for MeuBuffer {}

3. PhantomData para Seguranca de Tipos

use std::marker::PhantomData;

// Unidades de medida em nivel de tipos
struct Metros;
struct Quilometros;
struct Segundos;

struct Medida<Unidade> {
    valor: f64,
    _unidade: PhantomData<Unidade>,
}

impl<U> Medida<U> {
    fn new(valor: f64) -> Self {
        Medida {
            valor,
            _unidade: PhantomData,
        }
    }
}

// So permitimos somar medidas da mesma unidade
impl<U> std::ops::Add for Medida<U> {
    type Output = Medida<U>;

    fn add(self, other: Self) -> Self::Output {
        Medida::new(self.valor + other.valor)
    }
}

// Conversao especifica
impl Medida<Quilometros> {
    fn para_metros(self) -> Medida<Metros> {
        Medida::new(self.valor * 1000.0)
    }
}

fn calcular_velocidade(
    distancia: Medida<Metros>,
    tempo: Medida<Segundos>,
) -> f64 {
    distancia.valor / tempo.valor
}

fn main() {
    let d1 = Medida::<Metros>::new(100.0);
    let d2 = Medida::<Metros>::new(200.0);
    let total = d1 + d2; // OK: mesma unidade

    let km = Medida::<Quilometros>::new(5.0);
    let em_metros = km.para_metros();

    let tempo = Medida::<Segundos>::new(10.0);
    let vel = calcular_velocidade(total, tempo);
    println!("Velocidade: {vel} m/s");

    // ERRO de compilacao: nao pode somar metros com quilometros!
    // let errado = Medida::<Metros>::new(1.0) + Medida::<Quilometros>::new(1.0);
}

4. PhantomData para Controle de Lifetime

use std::marker::PhantomData;

struct Referencia<'a, T> {
    ptr: *const T,
    _lifetime: PhantomData<&'a T>,
}

impl<'a, T> Referencia<'a, T> {
    fn new(referencia: &'a T) -> Self {
        Referencia {
            ptr: referencia as *const T,
            _lifetime: PhantomData,
        }
    }

    fn obter(&self) -> &'a T {
        // SAFETY: o PhantomData garante que o lifetime 'a
        // e respeitado pelo borrow checker. O ponteiro e valido
        // enquanto a referencia original for valida.
        unsafe { &*self.ptr }
    }
}

fn main() {
    let valor = 42;
    let ref_wrapper = Referencia::new(&valor);
    println!("Valor: {}", ref_wrapper.obter());
}

5. Sized e ?Sized — Tipos de Tamanho Dinamico

use std::fmt::Display;

// Por padrao, parametros genericos sao Sized
fn funcao_sized<T>(val: T) -> T {
    val
}

// ?Sized permite tipos de tamanho desconhecido (como str, [T], dyn Trait)
// Mas so pode ser usado por referencia
fn imprimir<T: Display + ?Sized>(val: &T) {
    println!("{val}");
}

// Trait objects sao !Sized
fn processar(item: &dyn Display) {
    println!("Processando: {item}");
}

fn main() {
    // Tipos Sized: o tamanho e conhecido em compile-time
    let n: i32 = 42;
    funcao_sized(n);

    // Com ?Sized, podemos aceitar tanto Sized quanto !Sized
    imprimir("texto literal");           // &str (!Sized)
    imprimir(&String::from("String"));   // &String (Sized)
    imprimir(&42);                       // &i32 (Sized)

    // str e !Sized — nao pode existir como valor direto
    // let s: str = *"hello"; // ERRO!
    let s: &str = "hello"; // OK — referencia a um tipo !Sized

    // [T] e !Sized
    let slice: &[i32] = &[1, 2, 3];
    imprimir_tamanho(slice);
}

fn imprimir_tamanho<T: ?Sized>(val: &T) {
    println!("Tamanho da referencia: {} bytes", std::mem::size_of_val(&val));
    println!("Tamanho do valor: {} bytes", std::mem::size_of_val(val));
}

Padroes Comuns

Phantom Types para Estado em Compile-Time

Use tipos fantasma para codificar estados que sao verificados pelo compilador:

use std::marker::PhantomData;

struct Rascunho;
struct Revisado;
struct Publicado;

struct Documento<Estado> {
    titulo: String,
    conteudo: String,
    _estado: PhantomData<Estado>,
}

impl Documento<Rascunho> {
    fn new(titulo: String) -> Self {
        Documento {
            titulo,
            conteudo: String::new(),
            _estado: PhantomData,
        }
    }

    fn escrever(&mut self, texto: &str) {
        self.conteudo.push_str(texto);
    }

    fn enviar_para_revisao(self) -> Documento<Revisado> {
        Documento {
            titulo: self.titulo,
            conteudo: self.conteudo,
            _estado: PhantomData,
        }
    }
}

impl Documento<Revisado> {
    fn publicar(self) -> Documento<Publicado> {
        println!("Publicando: {}", self.titulo);
        Documento {
            titulo: self.titulo,
            conteudo: self.conteudo,
            _estado: PhantomData,
        }
    }
}

impl Documento<Publicado> {
    fn url(&self) -> String {
        format!("/artigos/{}", self.titulo.to_lowercase().replace(' ', "-"))
    }
}

Opt-Out de Traits Automaticas

Para remover Send ou Sync de um tipo, use PhantomData com um tipo que nao as implementa:

use std::marker::PhantomData;
use std::cell::Cell;

struct NaoSync {
    dados: i32,
    _nao_sync: PhantomData<Cell<()>>, // Cell nao e Sync
}

Quando Usar (e Quando Nao Usar)

Use std::marker quando:

  • Precisar de seguranca de tipos em nivel de compilacao (phantom types)
  • Trabalhar com FFI e precisar de controle de lifetime com PhantomData
  • Implementar Send/Sync manualmente para tipos com ponteiros raw
  • Precisar de ?Sized para aceitar trait objects e slices

Nao use std::marker quando:

  • Enums simples resolverem o problema de estados
  • Nao estiver fazendo programacao generica avancada
  • PhantomData tornar o codigo mais confuso sem ganho claro de seguranca
  • Puder confiar nas implementacoes automaticas de Send/Sync

Dica: PhantomData<T> tem tamanho zero — nao adiciona nenhum custo em runtime. E puramente uma instrucao ao compilador.


Veja Tambem