traits | A palavra-chave impl | Rust
- traits | A palavra-chave
impl| Rust
Rust é uma linguagem de sistemas moderna, que evita herança tradicional e foca em composição de comportamentos por meio de traits. Esses comportamentos são associados a tipos através de blocos impl.
O que é trait?
Em Rust, uma trait define um conjunto de métodos que um tipo pode implementar. É semelhante ao conceito de interface em linguagens como C# ou Java.
Características:
- Define o que um tipo pode fazer (contrato).
- Pode conter implementações padrão (como interfaces com default em C# 8+).
- Permite polimorfismo, via dispatch estático ou dinâmico.
- Usada como restrição em generics (trait bounds).
📎 Exemplo:
trait Desenhavel {
fn desenhar(&self);
}
Com implementação:
struct Circulo;
impl Desenhavel for Circulo {
fn desenhar(&self) {
println!("Desenhando um círculo");
}
}
O que é impl?
A palavra-chave impl é usada para associar funcionalidades a tipos, sejam essas funcionalidades métodos nativos ou implementações de traits.
Tipos de impl em Rust
1. impl Tipo — Implementação intrínseca
Define métodos e funções associadas diretamente para um tipo, sem necessidade de trait.
struct Pessoa {
nome: String,
idade: u8,
}
impl Pessoa {
fn nova(nome: &str) -> Self {
Pessoa {
nome: nome.to_string(),
idade: 0,
}
}
fn fazer_aniversario(&mut self) {
self.idade += 1;
}
}
2. impl Trait for Tipo — Implementação de trait
Permite que um tipo "assine" o contrato de uma trait, fornecendo os métodos obrigatórios.
trait Saudavel {
fn calorias(&self) -> u32;
}
struct Fruta;
impl Saudavel for Fruta {
fn calorias(&self) -> u32 {
45
}
}
3. impl<T> — Implementações genéricas
Permite definir lógica reutilizável para múltiplos tipos:
struct Caixa<T> {
valor: T,
}
impl<T> Caixa<T> {
fn novo(valor: T) -> Self {
Caixa { valor }
}
fn pegar(&self) -> &T {
&self.valor
}
}
4. impl<T> where ... — Implementações condicionais
Aplica métodos apenas quando certas condições são satisfeitas.
use std::fmt::Display;
impl<T> Caixa<T>
where
T: Display,
{
fn imprimir(&self) {
println!("Valor: {}", self.valor);
}
}
5. default impl — Implementação padrão em traits
Traits podem oferecer comportamento padrão, que os tipos podem sobrescrever.
trait Saudavel {
fn calorias(&self) -> u32 {
100 // valor padrão
}
}
Comparação: Trait (Rust) vs Interface (C#)
| Característica | Trait (Rust) | Interface (C#) |
|---|---|---|
| Contrato de métodos | ✔️ | ✔️ |
| Implementação padrão | ✔️ (desde Rust 1.0) | ✔️ (desde C# 8.0) |
| Múltiplas por tipo | ✔️ | ✔️ |
| Restrição em generics | ✔️ (T: Trait) | ✔️ (where T : IInterface) |
| Suporte a dispatch dinâmico | ✔️ (dyn Trait) | ✔️ (via virtual/interface) |
| Implementação externa | ✔️ (com limitações) | ❌ |
| Composição (extends múltiplos) | ✔️ (via +) | ✔️ (múltiplas interfaces) |
Uso de traits em generics
Com bound:
fn imprimir<T: std::fmt::Display>(valor: T) {
println!("{}", valor);
}
Com trait object (dispatch dinâmico):
fn imprimir(valor: &dyn std::fmt::Display) {
println!("{}", valor);
}
Composição de Traits
Você pode combinar múltiplos comportamentos:
fn processar<T: Desenhavel + Saudavel>(item: &T) {
item.desenhar();
println!("Calorias: {}", item.calorias());
}
Exemplo completo
trait Exibivel {
fn exibir(&self);
}
struct Produto {
nome: String,
preco: f64,
}
impl Exibivel for Produto {
fn exibir(&self) {
println!("{} - R$ {:.2}", self.nome, self.preco);
}
}
fn mostrar(item: &impl Exibivel) {
item.exibir();
}
Conclusão
- Use
traitpara definir comportamentos genéricos. - Use
implpara associar lógica a tipos. - Prefira generics com traits (
impl Trait,T: Trait) para máxima performance. - Use
dyn Traitapenas quando for necessária flexibilidade com custo de dispatch dinâmico. - Traits são mais poderosas que interfaces tradicionais, graças à composição, especialização, e implementação externa (limitada).