Capítulo 13: Traits, Trait Bounds, Derives e Generics
Capítulo 13: Traits, Trait Bounds, Derives e Generics
Especialização em Sistemas com Rust • Rust 2021 Edition • Ownership, Concorrência Segura e Sistemas de Alta Performance
🗺️ Mapa Conceitual de Polimorfismo Estático vs Polimorfismo Dinâmico
flowchart TD
Polimorfismo["Polimorfismo em Rust via Traits (Contratos de Comportamento)"] --> Estatico["Static Dispatch (Polimorfismo Estático)"]
Polimorfismo --> Dinamico["Dynamic Dispatch (Polimorfismo Dinâmico)"]
subgraph STATIC_DISPATCH["Monomorfização em Tempo de Compilação"]
Generics["Generics & Trait Bounds: fn processar<T: Trait>(x: T)"]
WhereClause["Cláusula where: where T: TraitA + TraitB"]
LLVM_Dup["LLVM gera cópias especializadas de código de máquina para cada tipo"]
CustoZero["Zero Custo de Runtime (Inline e Otimização Direta)"]
end
subgraph DYNAMIC_DISPATCH["Despacho em Tempo de Execução"]
TraitObjects["Trait Objects: &dyn Trait ou Box<dyn Trait>"]
FatPointer["Fat Pointer: [Ponteiro do Dado (8B) + Ponteiro da VTable (8B)]"]
ColecaoHeterogenea["Permite Coleções Heterogêneas (Vec<Box<dyn Trait>>)"]
end
Estatico --- STATIC_DISPATCH
STATIC_DISPATCH --> CustoZero
Dinamico --- DYNAMIC_DISPATCH
DYNAMIC_DISPATCH --> ColecaoHeterogenea
style Polimorfismo fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style Estatico fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style Dinamico fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style CustoZero fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
🏛️ 1. O Papel das Traits no Ecossistema Rust
Em Rust, as Traits definem contratos abstratos de comportamento que tipos concretos podem implementar. Elas equivalem conceitualmente a interfaces em linguagens como Java ou C#, mas com três diferenciais técnicos fundamentais:
- Implementação Externa (Orphan Rule): Você pode implementar traits de terceiros para seus próprios tipos, ou implementar suas próprias traits para tipos existentes (inclusive primitivos como
i32ouString). - Métodos com Implementação Padrão: As traits podem fornecer implementações default completas, exigindo que o desenvolvedor implemente apenas o método fundamental.
- Monomorfização com Custo Zero: O polimorfismo genérico padrão do Rust não utiliza tabelas de métodos virtuais em runtime; ele é resolvido e otimizado 100% durante a compilação.
⚙️ 2. Definindo e Implementando uma Trait
// 1. Definição da Trait
pub trait Criptografavel {
// Método obrigatório a ser implementado por cada tipo
fn serializar_bytes(&self) -> Vec<u8>;
// Método com implementação padrão (default method)
fn calcular_checksum(&self) -> u32 {
let bytes = self.serializar_bytes();
bytes.iter().map(|&b| b as u32).sum()
}
}
// 2. Struct de Domínio
pub struct MensagemAuditoria {
pub id: u64,
pub conteudo: String,
}
// 3. Implementação da Trait para a Struct
impl Criptografavel for MensagemAuditoria {
fn serializar_bytes(&self) -> Vec<u8> {
let mut buffer = self.id.to_be_bytes().to_vec();
buffer.extend_from_slice(self.conteudo.as_bytes());
buffer
}
}
🚀 3. Generics e Trait Bounds: A Força da Cláusula where
Ao escrever funções que operam sobre múltiplos tipos, utilizamos parâmetros genéricos com Trait Bounds para garantir que o tipo possui as capacidades requeridas:
use std::fmt::Debug;
// Sintaxe idiomática e limpa com cláusula 'where'
fn registrar_e_persistir<T>(registro: &T)
where
T: Criptografavel + Debug,
{
println!("Inspecionando registro (Debug): {:?}", registro);
let checksum = registro.calcular_checksum();
println!("Checksum criptográfico calculado: 0x{:08X}", checksum);
}
3.1 Monomorfização (Monomorphization)
Se você invocar registrar_e_persistir com MensagemAuditoria e depois com OutroTipo, o compilador rustc duplica o código da função gerando duas versões assembly independentes:
registrar_e_persistir_para_MensagemAuditoriaregistrar_e_persistir_para_OutroTipoCada versão recebe chamadas diretas de CPU sem ponteiros intermediários, permitindo ao LLVM aplicar inlining e vetorização como se você tivesse escrito código especializado à mão.
🎭 4. Despacho Dinâmico com Trait Objects (dyn Trait)
A monomorfização exige que o tipo concreto seja conhecido em tempo de compilação. Mas o que fazer quando precisamos de uma lista contendo tipos heterogêneos (por exemplo, um vetor de diferentes drivers de hardware: Sensor, Camera, Termometro)?
Nesse caso, recorre-se aos Trait Objects utilizando a palavra-chave dyn:
// Vetor heterogêneo na Heap usando Box<dyn Trait>
let hardware_pool: Vec<Box<dyn Criptografavel>> = vec![
Box::new(MensagemAuditoria { id: 1, conteudo: String::from("Auth") }),
Box::new(MensagemAuditoria { id: 2, conteudo: String::from("Kernel") }),
];
for item in &hardware_pool {
println!("Checksum via VTable: {}", item.calcular_checksum());
}
O que é o Fat Pointer?
Um ponteiro para dyn Trait ocupa 16 bytes na Stack (Fat Pointer):
- 8 bytes: Ponteiro para o dado real na Heap ou Stack.
- 8 bytes: Ponteiro para a VTable (Tabela Virtual de Funções gerada pelo compilador com os endereços de código daquele tipo específico).
🏷️ 5. Derivação Automática de Traits Comuns (#[derive(...)])
O Rust permite implementar dezenas de comportamentos essenciais automaticamente através de macros de derivação:
| Trait Padrão | Funcionalidade Fornecida | Exemplo de Uso |
|---|---|---|
Debug |
Formatação de depuração via {:?} ou {:#?} |
println!("{:?}", x) |
Clone |
Duplicação explícita via .clone() |
let b = a.clone() |
Copy |
Cópia bit a bit implícita na Stack (apenas tipos sem Heap) | let b = a |
PartialEq / Eq |
Comparação de igualdade (==, !=) |
if a == b |
PartialOrd / Ord |
Comparação de ordem (<, >, <=, >=) |
Ordenação de listas com .sort() |
Hash |
Cálculo de hash para chaves de mapas | Permite usar como chave de HashMap e HashSet |
⚖️ 6. Matriz de Decisão: Trait Bounds vs Trait Objects
| Critério | Trait Bounds (impl Trait / <T: Trait>) |
Trait Objects (dyn Trait) |
|---|---|---|
| Momento de Despacho | Tempo de Compilação (Estático) | Tempo de Execução (Dinâmico / VTable) |
| Custo de CPU | Zero Absoluto (Permite Inlining) | Pequena penalidade de indireção de ponteiro |
| Tamanho do Binário | Pode aumentar (código duplicado por tipo) | Menor (uma única função atende a todos) |
| Heterogeneidade | Homogêneo (apenas um tipo por instância) | Permite tipos mistos na mesma coleção |
⚠️ 7. Armadilhas Comuns e Mensagens do Compilador
- Tentativa de Usar Trait Objects sem
dyn:- Em versões antigas permitia-se
&Trait. No Rust 2021, o prefixodyné obrigatório:&dyn Trait. Omiti-lo gerará o aviso/erro:trait objects without an explicit 'dyn' are deprecated.
- Em versões antigas permitia-se
- A Regra do Órfão (Orphan Rule):
Você não pode implementar uma trait externa para um tipo externo (exemplo: implementar a trait
Displayda stdlib para a structDateTimede uma crate externa). Pelo menos um dos dois (a trait ou o tipo) deve pertencer à sua crate atual.
🛡️ 8. Boas Práticas Corporativas
- Prefira Static Dispatch por Padrão: Use
where T: Traitsempre que possível. Reservedyn Traitapenas quando a coleção realmente precisar abrigar tipos diferentes em tempo de execução.
📋 9. Cheat-Sheet: Resumo Executivo
// Definição e bounds
pub trait Plugin { fn run(&self); }
fn carregar<T: Plugin>(p: &T) { p.run(); } // Estático
fn carregar_dinamico(p: &dyn Plugin) { p.run(); } // Dinâmico
#[derive(Debug, Clone, PartialEq)]
struct Config { portas: u32 }