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:

  1. 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 i32 ou String).
  2. 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.
  3. 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_MensagemAuditoria
  • registrar_e_persistir_para_OutroTipo Cada 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

  1. Tentativa de Usar Trait Objects sem dyn:
    • Em versões antigas permitia-se &Trait. No Rust 2021, o prefixo dyn é obrigatório: &dyn Trait. Omiti-lo gerará o aviso/erro: trait objects without an explicit 'dyn' are deprecated.
  2. A Regra do Órfão (Orphan Rule): Você não pode implementar uma trait externa para um tipo externo (exemplo: implementar a trait Display da stdlib para a struct DateTime de 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: Trait sempre que possível. Reserve dyn Trait apenas 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 }