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
| Trait | Auto? | Descricao |
|---|---|---|
Copy | Nao | Tipo pode ser copiado bit-a-bit (implica Clone) |
Send | Sim | Tipo pode ser transferido entre threads |
Sync | Sim | &T pode ser compartilhado entre threads |
Sized | Sim | Tamanho conhecido em tempo de compilacao |
Unpin | Sim | Tipo pode ser movido de tras de Pin |
Unsize | — | Tipo pode ser “reduzido” (unstable) |
“Auto” significa que o compilador implementa a trait automaticamente quando todos os campos a satisfazem.
PhantomData
| Tipo | Descricao |
|---|---|
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/Syncmanualmente para tipos com ponteiros raw - Precisar de
?Sizedpara aceitar trait objects e slices
Nao use std::marker quando:
- Enums simples resolverem o problema de estados
- Nao estiver fazendo programacao generica avancada
PhantomDatatornar 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
- Send e Sync em Detalhes — aprofundamento sobre seguranca entre threads
- Unsafe Rust — quando implementar Send/Sync manualmente
- Modulo std::mem —
size_ofe layout de tipos - Pin<P> em Rust — relacao entre
UnpinePin - Modulo std::any — inspecao de tipos em runtime
- O Prelude do Rust — quais traits de marcacao sao importadas automaticamente
- Documentacao oficial — std::marker