E0038: Trait Não Pode Ser Trait Object no Rust

Como resolver o erro E0038 do Rust: o trait não pode ser usado como trait object. Entenda as regras de object safety e alternativas como enum dispatch e genéricos.

E0038: Trait Não Pode Ser Trait Object

O erro E0038 ocorre quando você tenta usar um trait como trait object (dyn Trait) mas o trait não satisfaz as regras de object safety (segurança de objeto). Nem todo trait pode ser usado com dyn — há restrições específicas.

A Mensagem de Erro

error[E0038]: the trait `Clonavel` cannot be made into an object
 --> src/main.rs:8:19
  |
8 |     let item: Box<dyn Clonavel> = Box::new(texto);
  |                   ^^^^^^^^^^^^ `Clonavel` cannot be made into an object
  |
note: for a trait to be "dyn-compatible" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
  = note: the trait cannot be made into an object because it requires `Self: Sized`

O Que Significa

Um trait object (dyn Trait) permite polimorfismo em tempo de execução — diferentes tipos que implementam o mesmo trait podem ser tratados uniformemente. Internamente, o Rust usa uma vtable (tabela de funções virtuais) para despachar as chamadas de método.

Para que isso funcione, o compilador precisa saber:

  1. O tamanho de cada chamada de método (não pode depender de Self)
  2. Qual método chamar (precisa ser resolvível pela vtable)

As regras de object safety exigem que o trait:

  • Não tenha métodos que retornem Self (o tipo concreto é desconhecido em runtime)
  • Não tenha parâmetros genéricos de tipo em métodos
  • Não exija Self: Sized (trait objects são !Sized)
  • Não tenha funções associadas sem self (não podem estar na vtable)

Código com Erro

Método que retorna Self

trait Clonavel {
    fn clonar(&self) -> Self;  // Retorna Self — não é object safe
}

fn processar(item: &dyn Clonavel) {  // ERRO
    let copia = item.clonar();
}

Método com parâmetro genérico

trait Conversor {
    fn converter<T>(&self) -> T;  // Genérico — não é object safe
}

fn processar(item: &dyn Conversor) {  // ERRO
    let valor: i32 = item.converter();
}

Trait com Self: Sized

trait Fabrica: Sized {  // Requer Sized — não é object safe
    fn criar() -> Self;
}

fn processar(item: &dyn Fabrica) {  // ERRO
}

Como Resolver

Solução 1: Usar where Self: Sized para Excluir Métodos

Marque os métodos problemáticos para que não entrem na vtable:

trait Clonavel {
    fn nome(&self) -> &str;

    // Este método não estará disponível via dyn Trait
    fn clonar(&self) -> Self where Self: Sized;
}

struct Texto {
    conteudo: String,
}

impl Clonavel for Texto {
    fn nome(&self) -> &str {
        &self.conteudo
    }

    fn clonar(&self) -> Self {
        Texto { conteudo: self.conteudo.clone() }
    }
}

fn processar(item: &dyn Clonavel) {
    // Pode usar `nome()`, mas não `clonar()`
    println!("Nome: {}", item.nome());
}

Solução 2: Retornar Box em Vez de Self

Em vez de retornar Self, retorne Box<dyn Trait>:

trait Animal {
    fn nome(&self) -> &str;
    fn filhote(&self) -> Box<dyn Animal>;  // Box em vez de Self
}

struct Gato {
    nome_gato: String,
}

impl Animal for Gato {
    fn nome(&self) -> &str {
        &self.nome_gato
    }

    fn filhote(&self) -> Box<dyn Animal> {
        Box::new(Gato {
            nome_gato: format!("Filhote de {}", self.nome_gato),
        })
    }
}

fn processar(animal: &dyn Animal) {
    let filho = animal.filhote();
    println!("Filhote: {}", filho.nome());
}

Solução 3: Usar Genéricos em Vez de Trait Objects

Se possível, use dispatch estático com genéricos em vez de dyn:

trait Conversor {
    fn para_string(&self) -> String;
}

// Em vez de `dyn Conversor`, use genérico:
fn processar<T: Conversor>(item: &T) {
    println!("{}", item.para_string());
}

// Ou com `impl Trait`:
fn processar_v2(item: &impl Conversor) {
    println!("{}", item.para_string());
}

Genéricos têm dispatch estático (resolvido em compilação) e geralmente são mais rápidos que trait objects, mas geram código duplicado para cada tipo concreto.

Solução 4: Usar Enum Dispatch

Se você tem um conjunto finito e conhecido de tipos, use um enum:

enum Forma {
    Circulo { raio: f64 },
    Retangulo { largura: f64, altura: f64 },
    Triangulo { base: f64, altura: f64 },
}

impl Forma {
    fn area(&self) -> f64 {
        match self {
            Forma::Circulo { raio } => std::f64::consts::PI * raio * raio,
            Forma::Retangulo { largura, altura } => largura * altura,
            Forma::Triangulo { base, altura } => base * altura / 2.0,
        }
    }

    fn nome(&self) -> &str {
        match self {
            Forma::Circulo { .. } => "Círculo",
            Forma::Retangulo { .. } => "Retângulo",
            Forma::Triangulo { .. } => "Triângulo",
        }
    }
}

fn main() {
    let formas: Vec<Forma> = vec![
        Forma::Circulo { raio: 5.0 },
        Forma::Retangulo { largura: 3.0, altura: 4.0 },
    ];

    for f in &formas {
        println!("{}: {:.2}", f.nome(), f.area());
    }
}

Enum dispatch é mais rápido que trait objects (sem indireção de ponteiro) e permite métodos que retornam Self.

Regras de Object Safety Resumidas

RegraObject Safe?
Método com &self / &mut selfSim
Método que retorna SelfNao
Método com genéricos de tipoNao
Trait com Self: SizedNao
Função associada sem selfNao
Método com where Self: SizedSim (excluído da vtable)

Veja Também