Traits em Rust: Guia Completo com Exemplos — 2026

Domine traits em Rust com este guia completo: definição, implementação, trait bounds, objetos trait, generics e as traits mais importantes da standard library.

Introdução

Traits são o coração do sistema de tipos do Rust. Se você vem de outras linguagens, pode pensar nelas como interfaces (Java/Go) ou type classes (Haskell) — mas traits em Rust vão além, habilitando polimorfismo, abstração e composição de comportamentos com segurança em tempo de compilação.

Neste guia, vamos do básico ao avançado: definir traits, implementar para tipos próprios e externos, usar trait bounds com generics, entender trait objects vs generics, e dominar as traits mais importantes da standard library.

Definindo e Implementando Traits

Uma trait define um conjunto de métodos que um tipo deve implementar:

trait Resumo {
    fn resumir(&self) -> String;
}

struct Artigo {
    titulo: String,
    autor: String,
    conteudo: String,
}

impl Resumo for Artigo {
    fn resumir(&self) -> String {
        format!("{}, por {}{}...", self.titulo, self.autor, &self.conteudo[..80])
    }
}

struct Tweet {
    usuario: String,
    texto: String,
}

impl Resumo for Tweet {
    fn resumir(&self) -> String {
        format!("@{}: {}", self.usuario, self.texto)
    }
}

Agora, qualquer função que aceite algo que implemente Resumo funciona com ambos os tipos:

fn exibir_resumo(item: &impl Resumo) {
    println!("Novo conteúdo: {}", item.resumir());
}

fn main() {
    let artigo = Artigo {
        titulo: "Rust em 2026".into(),
        autor: "Comunidade".into(),
        conteudo: "Rust continua crescendo em adoção global com novos casos de uso...".repeat(3),
    };

    let tweet = Tweet {
        usuario: "rustlang_br".into(),
        texto: "Traits são incríveis!".into(),
    };

    exibir_resumo(&artigo);
    exibir_resumo(&tweet);
}

Implementações Padrão (Default)

Traits podem fornecer implementações padrão que os tipos podem usar ou sobrescrever:

trait Notificacao {
    fn destinatario(&self) -> &str;
    fn conteudo(&self) -> &str;

    // Implementação padrão
    fn enviar(&self) -> String {
        format!(
            "Enviando para {}: {}",
            self.destinatario(),
            self.conteudo()
        )
    }
}

struct Email {
    para: String,
    assunto: String,
    corpo: String,
}

impl Notificacao for Email {
    fn destinatario(&self) -> &str {
        &self.para
    }

    fn conteudo(&self) -> &str {
        &self.corpo
    }

    // Sobrescreve a implementação padrão
    fn enviar(&self) -> String {
        format!(
            "📧 Email para {}: [{}] {}",
            self.para, self.assunto, self.corpo
        )
    }
}

struct SMS {
    numero: String,
    mensagem: String,
}

impl Notificacao for SMS {
    fn destinatario(&self) -> &str {
        &self.numero
    }

    fn conteudo(&self) -> &str {
        &self.mensagem
    }
    // Usa a implementação padrão de enviar()
}

Implementações padrão são poderosas porque permitem adicionar novos métodos a uma trait sem quebrar implementações existentes — um padrão muito usado na standard library.

Trait Bounds: Restringindo Generics

Trait bounds permitem especificar que um tipo genérico deve implementar certas traits. Existem três sintaxes equivalentes:

// Sintaxe 1: impl Trait (mais concisa)
fn notificar(item: &impl Resumo) {
    println!("{}", item.resumir());
}

// Sintaxe 2: Trait bound explícito (mais flexível)
fn notificar<T: Resumo>(item: &T) {
    println!("{}", item.resumir());
}

// Sintaxe 3: where clause (melhor para múltiplos bounds)
fn processar<T>(item: &T) -> String
where
    T: Resumo + std::fmt::Display,
{
    format!("Display: {} | Resumo: {}", item, item.resumir())
}

A cláusula where é especialmente útil quando você tem múltiplos parâmetros genéricos com bounds complexos:

fn comparar_e_resumir<T, U>(a: &T, b: &U) -> String
where
    T: Resumo + PartialOrd,
    U: Resumo + Clone,
{
    format!("A: {} | B: {}", a.resumir(), b.resumir())
}

Trait Objects vs Generics: Quando Usar Cada Um

Esta é uma das decisões mais importantes em Rust. Ambos habilitam polimorfismo, mas com trade-offs diferentes.

Generics (Dispatch Estático)

Com generics, o compilador gera código específico para cada tipo concreto (monomorphization):

fn maior_resumo<T: Resumo>(items: &[T]) -> &T {
    // Todos os items devem ser do MESMO tipo
    let mut maior = &items[0];
    for item in &items[1..] {
        if item.resumir().len() > maior.resumir().len() {
            maior = item;
        }
    }
    maior
}

Vantagens: performance máxima (sem overhead), inline possível, otimizações do compilador. Desvantagens: tamanho do binário aumenta com cada tipo concreto, todos os itens devem ser do mesmo tipo.

Trait Objects (Dispatch Dinâmico)

Com dyn Trait, a decisão de qual método chamar acontece em runtime via vtable:

fn exibir_todos(items: &[Box<dyn Resumo>]) {
    // Cada item pode ser um tipo DIFERENTE
    for item in items {
        println!("{}", item.resumir());
    }
}

fn main() {
    let items: Vec<Box<dyn Resumo>> = vec![
        Box::new(Artigo {
            titulo: "Rust 2026".into(),
            autor: "Equipe".into(),
            conteudo: "Novidades do ano...".repeat(10),
        }),
        Box::new(Tweet {
            usuario: "dev_br".into(),
            texto: "Traits são poderosas!".into(),
        }),
    ];

    exibir_todos(&items);
}

Vantagens: coleções heterogêneas, binário menor, mais flexibilidade. Desvantagens: overhead de indireção (vtable lookup), sem inline, trait deve ser object-safe.

Para um aprofundamento nesse tema, veja nosso artigo sobre trait objects vs generics.

Regra Prática

CenárioUse
Performance críticaGenerics
Coleção de tipos diferentesTrait objects
Bibliotecas públicasGenerics (mais flexível)
Plugins/extensõesTrait objects
Poucos tipos concretosGenerics
Muitos tipos concretosTrait objects (menor binário)

Traits Essenciais da Standard Library

Rust tem traits fundamentais que aparecem em quase todo código. Conhecê-las é essencial.

Display e Debug

Display e Debug controlam como um tipo é exibido:

use std::fmt;

struct Ponto {
    x: f64,
    y: f64,
}

// Debug pode ser derivado automaticamente
// #[derive(Debug)]

// Display deve ser implementado manualmente
impl fmt::Display for Ponto {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}

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

Clone e Copy

Clone e Copy definem como valores são duplicados:

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

#[derive(Clone, Debug)]
struct Matriz {
    dados: Vec<Vec<f64>>,  // Vec não é Copy, então Matriz também não pode ser
}

fn main() {
    let v1 = Vetor2D { x: 1.0, y: 2.0 };
    let v2 = v1;      // Copy implícito — v1 ainda é válido
    let v3 = v1;      // Funciona porque Vetor2D é Copy

    let m1 = Matriz { dados: vec![vec![1.0]] };
    let m2 = m1.clone();  // Clone explícito necessário
    // let m3 = m1;       // ERRO: m1 foi movido se não for Clone
}

From e Into

From e Into permitem conversões entre tipos:

struct Celsius(f64);
struct Fahrenheit(f64);

impl From<Celsius> for Fahrenheit {
    fn from(c: Celsius) -> Self {
        Fahrenheit(c.0 * 9.0 / 5.0 + 32.0)
    }
}

// Into é implementado automaticamente quando From existe
fn aquecer(temp: impl Into<Fahrenheit>) {
    let f: Fahrenheit = temp.into();
    println!("Temperatura: {}°F", f.0);
}

fn main() {
    aquecer(Celsius(100.0));     // Converte automaticamente
    aquecer(Fahrenheit(212.0));  // Já é Fahrenheit
}

Iterator

Iterator é talvez a trait mais usada em Rust. Implementá-la desbloqueia dezenas de métodos gratuitos:

struct Fibonacci {
    atual: u64,
    proximo: u64,
}

impl Fibonacci {
    fn new() -> Self {
        Fibonacci { atual: 0, proximo: 1 }
    }
}

impl Iterator for Fibonacci {
    type Item = u64;

    fn next(&mut self) -> Option<Self::Item> {
        let resultado = self.atual;
        self.atual = self.proximo;
        self.proximo = resultado + self.proximo;
        Some(resultado)
    }
}

fn main() {
    // Todos esses métodos vêm de graça ao implementar next()
    let soma: u64 = Fibonacci::new()
        .take(20)
        .filter(|n| n % 2 == 0)
        .sum();

    println!("Soma dos 20 primeiros Fibonacci pares: {}", soma);

    let primeiros: Vec<u64> = Fibonacci::new().take(10).collect();
    println!("Primeiros 10: {:?}", primeiros);
}

Para mais sobre iteradores, veja nosso artigo sobre iteradores em Rust.

Supertraits: Composição de Traits

Uma trait pode exigir que outra trait já esteja implementada:

use std::fmt;

// Imprimivel exige que Display já esteja implementado
trait Imprimivel: fmt::Display {
    fn imprimir(&self) {
        println!("[LOG] {}", self);
    }

    fn imprimir_com_borda(&self) {
        let texto = format!("{}", self);
        let borda = "═".repeat(texto.len() + 4);
        println!("╔{}╗", borda);
        println!("║  {}  ║", texto);
        println!("╚{}╝", borda);
    }
}

// Qualquer tipo que implemente Display pode implementar Imprimivel
impl Imprimivel for Ponto {}

Blanket Implementations

Você pode implementar uma trait para todos os tipos que satisfaçam uma condição:

trait Logavel {
    fn log(&self);
}

// Implementa Logavel para QUALQUER tipo que implemente Display
impl<T: fmt::Display> Logavel for T {
    fn log(&self) {
        println!("[{}] {}", chrono::Local::now().format("%H:%M:%S"), self);
    }
}

A standard library usa esse padrão extensivamente. Por exemplo, Into<U> é implementado automaticamente para qualquer T que implemente From<T>.

Conclusão

Traits são o mecanismo central de abstração em Rust. Dominar traits significa dominar Rust. Recapitulando os pontos principais:

  • Traits definem comportamentos que tipos podem implementar
  • Implementações padrão evitam código repetitivo
  • Trait bounds restringem generics de forma segura
  • Generics dão performance máxima; trait objects dão flexibilidade
  • Traits da stdlib como Display, Clone, From e Iterator aparecem em todo código Rust

Para se aprofundar, explore nossos artigos sobre closures (que usam traits Fn, FnMut e FnOnce), smart pointers (que dependem das traits Deref e Drop), e pattern matching avançado. Se está começando com Rust, nosso tutorial de traits e generics é um ótimo ponto de partida.

Traits não são apenas um recurso da linguagem — são a filosofia do Rust em ação: composição sobre herança, segurança sobre conveniência, e abstração sem custo em runtime.

Se você trabalha com múltiplas linguagens, vale comparar como cada uma resolve abstração: Python usa duck typing e protocolos, enquanto Kotlin oferece interfaces com implementações padrão semelhantes às traits do Rust.