Traits comuns da std
Nesta aula, exploramos os traits mais comuns da biblioteca padrão do Rust: Debug, Clone, Copy, PartialEq, Eq, Ord e Display. Aprendemos como implementá-los manualmente e como usar a macro `derive` para gerar implementações automaticamente, além de boas práticas para cada trait.
Nesta aula, vamos explorar os traits mais comuns da biblioteca padrão do Rust. Esses traits fornecem comportamentos fundamentais como impressão, clonagem, comparação e ordenação. Entender como e quando implementá-los é essencial para escrever código idiomático e reutilizável.
Traits em Rust são como interfaces em outras linguagens: definem um conjunto de métodos que um tipo deve implementar. A std oferece vários traits que são tão onipresentes que o compilador pode derivá-los automaticamente usando #[derive]. Vamos ver cada um deles em detalhes.
Debug, Clone, Copy
Debug é o trait usado para formatação de depuração. Ele é ativado com {:?} na macro println! ou format!. Implementá-lo permite imprimir o conteúdo de uma struct ou enum de forma legível para depuração. Quase todas as estruturas de dados devem implementar Debug.
Clone permite criar uma cópia explícita de um valor. Ao contrário de Copy, que é implícito, Clone exige uma chamada ao método .clone(). Tipos que alocam recursos (como String, Vec) implementam Clone, mas não Copy, porque uma cópia implícita poderia causar vazamentos ou dupla liberação.
Copy é um marcador que indica que o tipo pode ser copiado simplesmente com uma cópia de bits (como inteiros, booleanos, floats). Tipos que implementam Copy devem implementar Clone também. Copy é implícito: ao atribuir ou passar por valor, o original ainda pode ser usado. Apenas tipos que estão na pilha (sem heap) podem ser Copy.
#[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
println!("{:?}", p1); // Debug
let p3 = p1.clone(); // Clone explícito
println!("{:?}", p3);
}PartialEq, Eq, Ord
PartialEq define a igualdade (== e !=). Ele permite que alguns valores não sejam comparáveis (ex.: f64 com NaN). Eq é um subtrait de PartialEq que garante reflexividade total: todo valor é igual a si mesmo. Tipos inteiros e strings implementam Eq; floats implementam apenas PartialEq devido ao NaN.
Ord define uma ordenação total (<, >, <=, >=). Requer que o tipo também implemente Eq e PartialOrd. Com Ord, podemos usar .sort() e BinaryHeap. Para tipos que não têm uma ordem natural, implemente manualmente.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
struct Pessoa {
nome: String,
idade: u8,
}
fn main() {
let mut pessoas = vec![
Pessoa { nome: "Ana".into(), idade: 30 },
Pessoa { nome: "Bia".into(), idade: 25 },
];
pessoas.sort(); // Ordena por nome depois idade (ordem lexicográfica)
println!("{:?}", pessoas);
}Display
Display é usado para formatação amigável ao usuário, com {}. Diferente de Debug, que é para depuração, Display deve produzir uma saída legível para o usuário final. Não pode ser derivado automaticamente com derive; você precisa implementá-lo manualmente.
use std::fmt;
struct Temperatura {
celsius: f64,
}
impl fmt::Display for Temperatura {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}°C", self.celsius)
}
}
fn main() {
let t = Temperatura { celsius: 25.5 };
println!("{}", t); // 25.5°C
}derive
A macro #[derive] gera implementações automáticas de traits para structs e enums. É uma ferramenta poderosa que reduz código repetitivo. Os traits deriváveis comuns são: Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default. Para usar, basta adicionar #[derive(Trait1, Trait2, ...)] antes da definição do tipo.
Nem todos os traits podem ser derivados. Por exemplo, Display e From exigem implementação manual porque dependem de lógica específica do tipo. Além disso, a derivação só funciona se todos os campos do tipo implementarem o trait. Caso contrário, você receberá um erro de compilação.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct Livro {
titulo: String,
autor: String,
ano: u16,
}
fn main() {
let livro = Livro {
titulo: "O Senhor dos Anéis".into(),
autor: "J.R.R. Tolkien".into(),
ano: 1954,
};
println!("{:?}", livro);
let outro = livro.clone();
println!("{}", livro == outro); // true
}Boas práticas
- Sempre implemente
Debugpara seus tipos, mesmo que não planeje usá-lo imediatamente. É útil para depuração e para usar macros comoassert_eq!. - Use
Clonequando precisar de cópias explícitas, mas evite clonar grandes estruturas desnecessariamente. - Implemente
Copyapenas para tipos pequenos e que estejam inteiramente na pilha. UseClonepara tipos que alocam heap. - Para comparações, implemente
PartialEqeEqsempre que fizer sentido. Se seu tipo tem uma ordem natural, implementeOrd. - Não derive
Ordse a ordem padrão (lexicográfica) não for a desejada. Nesse caso, implemente manualmente. - Prefira
derivesempre que possível, mas não hesite em implementar manualmente quando a lógica padrão não atender.
Referências
- Documentação oficial do trait Debug
- Documentação oficial do trait Clone
- Documentação oficial do trait Copy
- Documentação oficial do trait PartialEq
- Documentação oficial do trait Ord
- Documentação oficial do trait Display
- Referência sobre a macro derive
Exercícios
Crie uma struct
Corcom camposr,g,bdo tipou8. Derive os traits Debug, Clone, Copy, PartialEq e Eq. Escreva um programa que testa a igualdade de duas cores e imprime uma delas com Debug.✓ Resposta:#[derive(Debug, Clone, Copy, PartialEq, Eq)] struct Cor { r: u8, g: u8, b: u8, } fn main() { let c1 = Cor { r: 255, g: 0, b: 0 }; let c2 = Cor { r: 255, g: 0, b: 0 }; println!("{:?}", c1); println!("{}", c1 == c2); }Implemente manualmente o trait Display para a struct
Cordo exercício anterior, exibindo a cor no formatoRGB(r, g, b). Teste comprintln!("{}", cor).✓ 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: 128, g: 128, b: 0 }; println!("{}", cor); }Crie um enum
Statuscom variantesAtivoeInativo. Derive Debug, Clone, Copy, PartialEq e Eq. Escreva uma função que aceitaStatuspor valor e imprime se é Ativo ou Inativo.✓ Resposta:#[derive(Debug, Clone, Copy, PartialEq, Eq)] enum Status { Ativo, Inativo, } fn verificar_status(s: Status) { if s == Status::Ativo { println!("Ativo"); } else { println!("Inativo"); } } fn main() { let s = Status::Ativo; verificar_status(s); println!("{:?}", s); // ainda válido por causa de Copy }Derive Ord para a struct
Pessoa(nome: String, idade: u8). Crie um vetor de pessoas e ordene-o. A ordem padrão será por nome depois idade. Verifique com println!.✓ Resposta:#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] struct Pessoa { nome: String, idade: u8, } fn main() { let mut pessoas = vec![ Pessoa { nome: "Zeca".into(), idade: 20 }, Pessoa { nome: "Ana".into(), idade: 30 }, Pessoa { nome: "Ana".into(), idade: 25 }, ]; pessoas.sort(); println!("{:?}", pessoas); }Implemente manualmente o trait PartialEq para uma struct
Medidaque contém um campovalor: f64. Duas medidas são iguais se seus valores são iguais (considere que NaN não é igual a nada). Teste com valores normais e com NaN.✓ Resposta:struct Medida { valor: f64, } impl PartialEq for Medida { fn eq(&self, other: &Self) -> bool { self.valor == other.valor } } fn main() { let a = Medida { valor: 3.14 }; let b = Medida { valor: 3.14 }; let c = Medida { valor: f64::NAN }; println!("{}", a == b); // true println!("{}", c == c); // false }