Traits comuns da std
Esta aula explora os traits mais comuns da biblioteca padrão do Rust: Debug, Clone, Copy, PartialEq, Eq, Ord, Display e o atributo derive. Você aprenderá como implementá-los manualmente ou via derive, e como eles habilitam comportamentos essenciais como impressão, comparação e clonagem de tipos.
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
- Documentação do trait Debug
- Documentação do trait Clone
- Documentação do trait Copy
- Documentação do trait PartialEq
- Documentação do trait Eq
- Documentação do trait Ord
- Documentação do trait Display
- O atributo derive na referência Rust
- Padrões de uso de derive
Exercícios
Crie um struct
Corcom camposr,g,bdo tipou8. Derive os traitsDebug,CloneeCopy. 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); }Implemente o trait
Displaypara o structCordo exercício anterior, de forma que a saída seja no formatoRGB(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) }Crie um enum
Statuscom variantesAtivoeInativo. DeriveDebug,Clone,Copy,PartialEqeEq. 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 }Derive
PartialOrdeOrdpara um structNotaque contém um campovalordo tipof64. Explique por que isso não é possível diretamente comderivee como você contornaria?✓ Resposta:Não é possível derivar
Ordparaf64porquef64não implementaOrd(devido ao NaN). Uma alternativa é usar um tipo que implementeOrd, comoordered_float::OrderedFloatda crateordered_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 }Crie um struct
Pessoacom camposnome(String) eidade(u8). Implemente manualmentePartialEqde 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 }