Na biblioteca padrão do Rust, existem diversos traits que são tão frequentemente utilizados que a própria linguagem oferece suporte especial para eles. Esses traits definem comportamentos fundamentais, como a capacidade de imprimir um valor, cloná-lo, compará-lo por igualdade ou ordenação. Dominar esses traits é essencial para escrever código idiomático e eficiente em Rust.

Nesta aula, vamos explorar os traits mais comuns: Debug, Clone, Copy, PartialEq, Eq, Ord e Display. Veremos como implementá-los manualmente, mas principalmente como usar o atributo derive para gerar implementações automaticamente. Também discutiremos as relações entre esses traits e quando cada um é apropriado.

Debug, Clone, Copy

O trait Debug é usado para formatar um valor de maneira adequada para depuração. Ele é frequentemente utilizado com a macro println!("{:?}", valor) ou com a macro dbg!. A implementação do Debug pode ser derivada automaticamente se todos os campos do struct ou enum também implementarem Debug.

O trait Clone permite criar uma cópia explícita de um valor, através do método .clone(). Ele é útil quando você precisa duplicar um dado sem consumir o original. Já o trait Copy é um marcador que indica que o tipo pode ser copiado implicitamente (por cópia de bits, como inteiros). Tipos que implementam Copy também devem implementar Clone, mas o contrário não é verdadeiro. Copy não pode ser implementado para tipos que contêm dados alocados no heap (como String ou Vec).

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

fn main() {
    let p1 = Ponto { x: 10, y: 20 };
    let p2 = p1; // Copy: p1 ainda é válido
    let p3 = p1.clone(); // Clone explícito
    println!("{:?}", p3); // Debug
}

PartialEq, Eq, Ord

O trait PartialEq define a comparação de igualdade (== e !=). Ele exige a implementação do método eq. O trait Eq é um marcador que indica que a relação de igualdade é reflexiva (a == a é sempre verdadeiro). Para tipos que podem ter valores não comparáveis (como f32 com NaN), implementa-se apenas PartialEq, sem Eq.

O trait Ord define a ordenação total (<, >, <=, >=). Ele requer que o tipo também implemente PartialOrd e Eq. A implementação de Ord deve ser consistente com PartialOrd. Geralmente, usamos derive para implementar esses traits quando a ordem natural (lexicográfica) é suficiente.

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
struct Idade(u32);

fn main() {
    let a = Idade(25);
    let b = Idade(30);
    println!("{:?}", a == b); // false
    println!("{:?}", a < b);  // true
}

Display

O trait Display é usado para formatar um valor para exibição ao usuário final (com {}). Diferente de Debug, que é voltado para depuração, Display deve produzir uma saída mais legível e amigável. Não é possível derivar Display automaticamente; você deve implementá-lo manualmente.

A implementação de Display requer o método fmt, que recebe um Formatter e retorna um Result. É comum usar a macro write! para escrever no formatador.

use std::fmt;

struct Pessoa {
    nome: String,
    idade: u8,
}

impl fmt::Display for Pessoa {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{} ({} anos)", self.nome, self.idade)
    }
}

fn main() {
    let p = Pessoa { nome: "Alice".to_string(), idade: 30 };
    println!("{}", p); // Alice (30 anos)
}

derive

O atributo #[derive(...)] permite que o compilador gere implementações automáticas de certos traits para structs, enums e unions. Os traits suportados incluem Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord e Hash. Para usar derive, todos os campos do tipo devem implementar o trait correspondente.

Derivar é conveniente e reduz a repetição de código, mas em alguns casos você pode precisar de um comportamento personalizado que justifique a implementação manual. Por exemplo, se a comparação de igualdade não for simplesmente comparar todos os campos, ou se você quiser uma ordem diferente da lexicográfica.

#[derive(Debug, Clone, PartialEq, Eq)]
struct Livro {
    titulo: String,
    autor: String,
    ano: u16,
}

fn main() {
    let livro1 = Livro {
        titulo: "O Senhor dos Anéis".to_string(),
        autor: "J.R.R. Tolkien".to_string(),
        ano: 1954,
    };
    let livro2 = livro1.clone();
    println!("{:?}", livro1 == livro2); // true
}

Boas práticas

Sempre que possível, derive os traits Debug, Clone e PartialEq para seus tipos, pois eles são úteis para depuração e testes. Se o tipo for pequeno e composto apenas por dados copiáveis, considere também derivar Copy. Evite implementar Display para tipos que são apenas dados internos; use Debug para esses casos. Lembre-se de que Copy é um marcador e não pode ser implementado para tipos que gerenciam recursos (como String).

Referências

Exercícios

  1. Crie um struct Cor com campos r, g, b do tipo u8. Derive os traits Debug, Clone e Copy. Em seguida, crie duas variáveis e demonstre que a cópia implícita funciona.

    ✓ Resposta:
    #[derive(Debug, Clone, Copy)]
    struct Cor {
        r: u8,
        g: u8,
        b: u8,
    }
    
    fn main() {
        let c1 = Cor { r: 255, g: 0, b: 0 };
        let c2 = c1; // Copy: c1 ainda é válido
        println!("{:?}", c1);
        println!("{:?}", c2);
    }
    
  2. Implemente o trait Display para o struct Cor do exercício anterior, de forma que a saída seja no formato RGB(r, g, b).

    ✓ Resposta:
    use std::fmt;
    
    impl fmt::Display for Cor {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            write!(f, "RGB({}, {}, {})", self.r, self.g, self.b)
        }
    }
    
    fn main() {
        let cor = Cor { r: 255, g: 0, b: 0 };
        println!("{}", cor); // RGB(255, 0, 0)
    }
    
  3. Crie um enum Status com variantes Ativo e Inativo. Derive Debug, Clone, Copy, PartialEq e Eq. Verifique se dois valores são iguais.

    ✓ Resposta:
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    enum Status {
        Ativo,
        Inativo,
    }
    
    fn main() {
        let s1 = Status::Ativo;
        let s2 = Status::Ativo;
        println!("{:?}", s1 == s2); // true
    }
    
  4. Derive PartialOrd e Ord para um struct Nota que contém um campo valor do tipo f64. Explique por que isso não é possível diretamente com derive e como você contornaria?

    ✓ Resposta:

    Não é possível derivar Ord para f64 porque f64 não implementa Ord (devido ao NaN). Uma alternativa é usar um tipo que implemente Ord, como ordered_float::OrderedFloat da crate ordered_float, ou implementar manualmente ignorando NaN. Exemplo com crate externa:

    use ordered_float::OrderedFloat;
    
    #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
    struct Nota {
        valor: OrderedFloat<f64>,
    }
    
    fn main() {
        let n1 = Nota { valor: OrderedFloat(8.5) };
        let n2 = Nota { valor: OrderedFloat(7.0) };
        println!("{:?}", n1 > n2); // true
    }
    
  5. Crie um struct Pessoa com campos nome (String) e idade (u8). Implemente manualmente PartialEq de forma que duas pessoas sejam consideradas iguais se tiverem o mesmo nome (ignorando a idade).

    ✓ Resposta:
    struct Pessoa {
        nome: String,
        idade: u8,
    }
    
    impl PartialEq for Pessoa {
        fn eq(&self, other: &Self) -> bool {
            self.nome == other.nome
        }
    }
    
    fn main() {
        let p1 = Pessoa { nome: "Alice".to_string(), idade: 30 };
        let p2 = Pessoa { nome: "Alice".to_string(), idade: 25 };
        println!("{}", p1 == p2); // true
    }