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:
- O tamanho de cada chamada de método (não pode depender de
Self) - 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
| Regra | Object Safe? |
|---|---|
Método com &self / &mut self | Sim |
Método que retorna Self | Nao |
| Método com genéricos de tipo | Nao |
Trait com Self: Sized | Nao |
Função associada sem self | Nao |
Método com where Self: Sized | Sim (excluído da vtable) |