Traits da biblioteca padrão: Display, Debug, Clone, Copy

1. Introdução aos Traits Essenciais da Biblioteca Padrão

Rust possui um sistema de traits que define comportamentos compartilhados entre tipos. Quatro traits da biblioteca padrão — Display, Debug, Clone e Copy — são fundamentais para o desenvolvimento cotidiano. Eles habilitam funcionalidades básicas de formatação para usuários finais, depuração de código e gerenciamento eficiente de memória.

Cada um desses traits atende a um propósito específico: Display controla como um tipo é exibido para humanos, Debug fornece saída detalhada para desenvolvedores, Clone permite duplicação explícita de dados, e Copy possibilita cópia implícita por valor. O compilador pode derivar automaticamente Debug, Clone e Copy usando #[derive(...)], mas Display sempre requer implementação manual.

2. Trait Display: Formatação para Usuários Finais

O trait Display do módulo std::fmt define como um tipo é formatado para consumo humano. Sua implementação exige o método fmt, que recebe um Formatter e retorna um Result.

use std::fmt;

struct Temperatura {
    celsius: f64,
}

impl fmt::Display for Temperatura {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{:.1}°C", self.celsius)
    }
}

fn main() {
    let temp = Temperatura { celsius: 23.5 };
    println!("Temperatura atual: {}", temp);
    // Saída: Temperatura atual: 23.5°C
}

O placeholder {} em macros como println!, print! e format! invoca o trait Display. Para tipos que não implementam Display, o compilador emitirá um erro. É boa prática implementar Display para tipos que representam conceitos do domínio do problema, como IDs de usuário, endereços ou valores monetários.

3. Trait Debug: Formatação para Depuração e Logging

Diferente de Display, o trait Debug é projetado para desenvolvedores. Ele pode ser derivado automaticamente com #[derive(Debug)] para structs e enums cujos campos também implementam Debug.

#[derive(Debug)]
struct Usuario {
    id: u32,
    nome: String,
    ativo: bool,
}

fn main() {
    let usuario = Usuario {
        id: 42,
        nome: String::from("Alice"),
        ativo: true,
    };
    println!("{:?}", usuario);
    // Saída: Usuario { id: 42, nome: "Alice", ativo: true }

    println!("{:#?}", usuario); // Formatação pretty-print
}

O placeholder {:?} ativa a formatação de depuração. Para controle fino, é possível implementar Debug manualmente:

use std::fmt;

struct Senha(String);

impl fmt::Debug for Senha {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Senha(***)")
    }
}

fn main() {
    let senha = Senha(String::from("segredo123"));
    println!("{:?}", senha); // Saída: Senha(***)
}

Debug é essencial para logging e para uso com macros como assert_eq! e panic!, que exibem valores quando testes falham.

4. Trait Clone: Duplicação Explícita de Dados

O trait Clone permite criar uma cópia independente de um valor através do método clone(). Pode ser derivado automaticamente quando todos os campos implementam Clone.

#[derive(Clone)]
struct Documento {
    titulo: String,
    conteudo: String,
    versao: u32,
}

fn main() {
    let original = Documento {
        titulo: String::from("Relatório"),
        conteudo: String::from("Conteúdo importante"),
        versao: 1,
    };

    let copia = original.clone();
    // Agora temos dois valores independentes na heap
}

A implementação manual de Clone é necessária quando a cópia superficial não é suficiente. Por exemplo, ao clonar uma struct que contém um ponteiro para um recurso externo:

struct BufferAlinhado {
    dados: Vec<u8>,
    alinhamento: usize,
}

impl Clone for BufferAlinhado {
    fn clone(&self) -> Self {
        let mut novos_dados = Vec::with_capacity(self.dados.len());
        novos_dados.extend_from_slice(&self.dados);
        BufferAlinhado {
            dados: novos_dados,
            alinhamento: self.alinhamento,
        }
    }
}

Tipos como String, Vec<T> e HashMap<K, V> implementam Clone, realizando cópias profundas (deep copy) que duplicam dados na heap.

5. Trait Copy: Cópia Implícita por Bitwise

O trait Copy indica que um tipo pode ser duplicado simplesmente copiando seus bits na memória. Tipos que implementam Copy não têm ownership movido em atribuições ou passagem de argumentos — o valor é implicitamente copiado.

#[derive(Debug, Copy, Clone)]
struct Ponto {
    x: f64,
    y: f64,
}

fn mover(p: Ponto) {
    println!("Ponto movido: {:?}", p);
}

fn main() {
    let p1 = Ponto { x: 10.0, y: 20.0 };
    mover(p1);           // p1 é copiado, não movido
    println!("Ainda disponível: {:?}", p1); // Funciona!
}

Para implementar Copy, o tipo deve:
- Implementar Clone (todo Copy é Clone)
- Ter tamanho fixo conhecido em tempo de compilação
- Não conter alocações dinâmicas (como String, Vec ou ponteiros inteligentes)

Tipos primitivos (i32, f64, bool, char), tuplas de tipos Copy, e arrays de tamanho fixo implementam Copy. Já String e Vec não podem implementar Copy porque gerenciam memória alocada na heap.

6. Interações e Diferenças entre Clone e Copy

A relação entre Clone e Copy é hierárquica: Copy é um marcador que estende Clone. Todo tipo Copy também é Clone, mas a recíproca não é verdadeira.

#[derive(Clone)]
struct ArquivoLog {
    nome: String,      // String: apenas Clone
    tamanho: u64,      // u64: Clone + Copy
}

fn processar<T: Clone>(item: T) {
    let _copia = item.clone(); // Clone sempre funciona
}

fn main() {
    let log = ArquivoLog {
        nome: String::from("app.log"),
        tamanho: 1024,
    };
    processar(log.clone()); // Clone explícito necessário

    let numero: u64 = 42;
    processar(numero); // Copy implícito, clone() não é chamado
}

A escolha entre Clone e Copy impacta a semântica do código:
- Use Copy para tipos pequenos e de baixo custo de cópia (números, flags booleanas, pontos 2D/3D)
- Use Clone para tipos que contêm dados alocados (strings, vetores, buffers)
- Implementar Copy em tipos grandes pode causar overhead inesperado em cópias implícitas

7. Boas Práticas e Casos de Uso Comuns

Ao trabalhar com esses traits, considere as seguintes diretrizes:

Derivação automática vs implementação manual:
- Prefira #[derive(Debug, Clone, Copy)] para structs simples sem lógica especial
- Implemente manualmente Display sempre que o tipo for exibido para usuários
- Implemente Clone manualmente quando a cópia superficial não for semanticamente correta

Otimização de desempenho:
- Para tipos pequenos (até 32 bytes aproximadamente), Copy é eficiente e evita chamadas explícitas a clone()
- Para tipos grandes com alocação dinâmica, Clone é a escolha correta, mas use com moderação para evitar overhead

Combinação com outros traits:
- Debug + PartialEq é uma combinação comum para testes com assert_eq!
- Clone + Hash + Eq permite usar tipos personalizados como chaves de HashMap

#[derive(Debug, Clone, PartialEq, Hash)]
struct ItemCatalogo {
    id: u32,
    nome: String,
}

fn main() {
    let item = ItemCatalogo {
        id: 1,
        nome: String::from("Martelo"),
    };

    // Usado como chave de HashMap
    use std::collections::HashMap;
    let mut inventario = HashMap::new();
    inventario.insert(item.clone(), 5);

    // Comparação em testes
    assert_eq!(item, item.clone());
}

Dominar esses quatro traits é essencial para escrever código Rust idiomático. Eles formam a base para interoperação com a biblioteca padrão e bibliotecas externas, garantindo que seus tipos se comportem de maneira previsível em contextos de formatação, depuração e gerenciamento de memória.

Referências