Erro E0790 do Rust: cannot call associated function on trait (falta o tipo)
O E0790 quer dizer que você pediu a um trait algo que só um tipo pode dar. Default::default() sozinho é como dizer “me dá o padrão” sem dizer o padrão de quê. É um erro comum, e a correção costuma ser colocar o nome do tipo na frente: Settings::default() em vez de Default::default(). Esta página mostra as correções primeiro e depois explica a diferença entre um trait e um tipo, para esse erro parar de surpreender você. Para os outros erros desta série, veja Erros do compilador Rust.
A mensagem de erro
Seção intitulada “A mensagem de erro”error[E0790]: cannot call associated function on trait without specifying the corresponding `impl` type --> src/main.rs:8:20 |8 | let settings = Default::default(); // ❌ o padrão de qual tipo? | ^^^^^^^^^^^^^^^^^^ cannot call associated function of trait |help: use a fully-qualified path to a specific available implementation |8 | let settings = </* self type */ as Default>::default(); // ❌ o padrão de qual tipo? | +++++++++++++++++++ +Em português: “não é possível chamar uma função associada de um trait sem especificar o tipo do impl correspondente”. A ajuda (help) sugere um caminho totalmente qualificado com o tipo que você quer.
Este é o programa que gera o erro:
#[derive(Debug, Default)]struct Settings { volume: u8, dark_mode: bool,}
fn main() { let settings = Default::default(); // ❌ o padrão de qual tipo? println!("{:?}", settings);}Você recebe o mesmo erro quando chama uma função do seu próprio trait pelo nome do trait, como Shape::new(). Aqui o rustc vê dois tipos que implementam Shape, então lista os dois como opções:
error[E0790]: cannot call associated function on trait without specifying the corresponding `impl` type --> src/main.rs:33:17 | 2 | fn new() -> Self; | ----------------- `Shape::new` defined here...33 | let shape = Shape::new(); | ^^^^^^^^^^^^ cannot call associated function of trait |help: use a fully-qualified path to one of the available implementations |33 | let shape = <Circle as Shape>::new(); | ++++++++++ +33 | let shape = <Square as Shape>::new(); | ++++++++++ +O rustc mostra onde Shape::new foi definida e sugere as duas implementações que encontrou: <Circle as Shape> e <Square as Shape>.
Para uma constante de trait, como Plugin::ID, a primeira linha muda um pouco: cannot refer to the associated constant on trait without specifying the corresponding `impl` type (não é possível usar a constante associada do trait sem especificar o tipo). Mesmo problema, mesmas correções.
O que significa
Seção intitulada “O que significa”Veja o que o rustc marca:
^^^^embaixo deDefault::default(): “cannot call associated function of trait” (não é possível chamar uma função associada do trait).defaulté uma função que pertence ao traitDefault, e você a chamou pelo nome do trait.- A ajuda sugere
</* self type */ as Default>::default(). A parte/* self type */é um espaço em branco para você preencher. O rustc está perguntando: “odefaultde qual tipo você quer?” - No exemplo do
Shape, o rustc preenche o espaço para você com cada tipo que encontrou (Circle,Square). Se só um tipo implementa o trait, ele sugere só esse.
Esta é a ideia central. Um trait é uma promessa: “qualquer tipo que implementa Default sabe criar um valor inicial de si mesmo”. Um tipo é quem cumpre a promessa: Settings, String, u32 e Vec<T> cumprem cada um do seu jeito. Default::default() pede um valor à própria promessa, mas uma promessa não fabrica nada. Só um tipo fabrica.
Pense numa placa de “temos delivery”. Muitos restaurantes colocam essa placa na vitrine. Mas você não pode pedir o jantar para a placa; precisa escolher um restaurante. Settings::default() é escolher o restaurante.
Por que o Rust faz isso
Seção intitulada “Por que o Rust faz isso”A maioria dos métodos de trait recebe self, como circle.area(). Esses nunca causam o E0790, porque o valor diz o tipo ao Rust: circle é um Circle, então ele chama o area de Circle. Você pode ler mais sobre isso em Traits.
Algumas funções de trait não têm self: construtores como new() e default(), conversões como From::from e constantes de trait como ID. Sem um valor para olhar, o Rust só tem dois lugares para descobrir o tipo: o nome que você escreve na frente (Settings::), ou o tipo que o resultado precisa ter (let s: Settings = ...). Se nenhum dos dois estiver lá, o Rust não consegue adivinhar. Muitos tipos implementam Default, e escolher um ao acaso mudaria em silêncio o que o seu programa faz.
É a mesma coisa que uma interface em Java ou C#. Você não pode chamar new numa interface; precisa de uma classe que a implemente. O Rust só avisa isso em tempo de compilação, mostrando o lugar exato a corrigir.
Como corrigir
Seção intitulada “Como corrigir”Correção 1: Chame a função no tipo
Seção intitulada “Correção 1: Chame a função no tipo”Escolha esta quase sempre. Troque o nome do trait pelo nome do tipo. É a correção mais curta, e lê bem: “o Settings padrão”, “um Circle novo”:
#[derive(Debug, Default)]struct Settings { volume: u8, dark_mode: bool,}
trait Shape { fn new() -> Self; fn name() -> String;}
struct Circle { radius: f64,}
impl Shape for Circle { fn new() -> Self { Circle { radius: 1.0 } } fn name() -> String { String::from("círculo") }}
fn main() { let settings = Settings::default(); // em vez de Default::default() let shape = Circle::new(); // em vez de Shape::new()
println!("{:?}", settings); println!("Um {} de raio {}", Circle::name(), shape.radius);}Saída:
Settings { volume: 0, dark_mode: false }Um círculo de raio 1Circle::new() funciona mesmo com new definido no trait, e não num bloco impl Circle. O Rust procura new em Circle, encontra por meio de impl Shape for Circle e chama. O trait precisa estar no escopo (definido no mesmo arquivo, ou trazido com use).
Correção 2: Anote o tipo da variável
Seção intitulada “Correção 2: Anote o tipo da variável”Escolha esta quando você gosta do estilo Default::default(), ou quando o nome do tipo é longo. Escreva o tipo na variável, e o Rust descobre a partir dele qual default chamar:
#[derive(Debug, Default)]struct Settings { volume: u8, dark_mode: bool,}
fn main() { let settings: Settings = Default::default(); let small: u8 = 42; let big: u32 = From::from(small);
println!("{:?}", settings); println!("{}", big);}Saída:
Settings { volume: 0, dark_mode: false }42Isso só funciona quando a função retorna o próprio tipo (Self), como fazem default(), new() e from(). Não funciona para fn name() -> String: saber que a resposta é uma String não diz ao Rust de qual forma você está falando. Nesses casos, use a Correção 1 ou a 3. Mais sobre anotações em Tipos de dados.
Correção 3: Sintaxe totalmente qualificada <Type as Trait>::function()
Seção intitulada “Correção 3: Sintaxe totalmente qualificada <Type as Trait>::function()”Esta é a forma “diga tudo” que o rustc sugere. Leia <Circle as Shape>::new() como “o new que Circle tem porque implementa Shape”. Ela sempre funciona, mas Circle::new() é mais fácil de ler, então você só precisa dela quando a forma curta é ambígua:
- o tipo implementa dois traits com uma função de mesmo nome, ou
- o tipo tem uma função própria com esse nome, e você quer a versão do trait.
Aqui uma Sale pode ser escrita como uma linha de CSV ou como um relatório impresso, e os dois traits têm um header(). Escrever Sale::header() dá o E0034 (em inglês), “multiple applicable items in scope” (vários itens aplicáveis no escopo), então você diz qual quer:
trait Csv { fn header() -> String;}
trait Report { fn header() -> String;}
struct Sale;
impl Csv for Sale { fn header() -> String { String::from("date,amount") }}
impl Report for Sale { fn header() -> String { String::from("Vendas do mês") }}
fn main() { println!("{}", <Sale as Csv>::header()); println!("{}", <Sale as Report>::header());}Saída:
date,amountVendas do mêsA lição Traits avançados tem outro exemplo, também com métodos que recebem &self.
Correção 4: Em código genérico, use T:: e o turbofish
Seção intitulada “Correção 4: Em código genérico, use T:: e o turbofish”Escolha esta quando você está escrevendo uma função genérica. Dentro de fn register<T: Plugin>(), o tipo é T, então chame T::name() e T::ID, e não Plugin::name(). Na hora de chamar a função, passe o tipo com o turbofish ::<Type>, porque não há nenhum argumento de onde o Rust possa descobri-lo:
trait Plugin { const ID: u32; fn name() -> String;}
struct Logger;struct Backup;
impl Plugin for Logger { const ID: u32 = 7; fn name() -> String { String::from("logger") }}
impl Plugin for Backup { const ID: u32 = 12; fn name() -> String { String::from("backup") }}
fn register<T: Plugin>() -> String { format!("Plugin #{}: {}", T::ID, T::name())}
fn main() { println!("{}", register::<Logger>()); println!("{}", register::<Backup>());}Saída:
Plugin #7: loggerPlugin #12: backupSe você deixar o turbofish de fora e escrever register(), recebe o E0283: “cannot infer type of the type parameter T” (não é possível inferir o tipo do parâmetro de tipo T). Aqui T também é o tipo, então T:: funciona do mesmo jeito que Circle:: na Correção 1. Veja Genéricos para saber mais sobre parâmetros de tipo.
Onde você costuma ver esse erro
Seção intitulada “Onde você costuma ver esse erro”Default::default() sem nenhuma pista. let config = Default::default(); falha, mas a mesma chamada funciona onde o tipo já é conhecido: num campo de struct, num argumento de função, ou na sintaxe de “preencher o resto”:
let settings = Settings { volume: 8, ..Default::default() }; // OK: o Rust sabe que é SettingsCorrija a linha que falha com Settings::default() ou let config: Settings = Default::default();. Veja Traits comuns.
Dentro de uma função genérica. Escrever o nome do trait em vez de T:
error[E0790]: cannot call associated function on trait without specifying the corresponding `impl` type --> src/main.rs:9:29 |2 | fn name() -> String; | -------------------- `Shape::name` defined here...9 | format!("This is a {}", Shape::name()) | ^^^^^^^^^^^^^ cannot call associated function of trait |help: use the fully-qualified path to the only available implementation |9 | format!("This is a {}", <Circle as Shape>::name()) | ++++++++++ +Cuidado: o rustc sugere <Circle as Shape> porque Circle é a única forma até agora. Isso compila, mas aí a função sempre devolveria o nome do círculo, qualquer que seja o T. A correção certa é T::name(), como na Correção 4.
Constantes de trait. println!("{}", Plugin::ID); pede um número à promessa, e só cada plugin tem o seu. Correção: Logger::ID, ou T::ID em código genérico.
From::from(x) sem um destino. let total = From::from(42u8); dá E0790, porque muitos tipos podem ser criados a partir de um u8. Correção: u32::from(42u8), ou let total: u32 = 42u8.into();. Veja Conversões.
Erros relacionados
Seção intitulada “Erros relacionados”- E0282: type annotations needed (faltam anotações de tipo). Se você usa o valor depois, como
let c = Shape::new();seguido dec.area(), o rustc mostra o E0282 no lugar. As mesmas correções funcionam. - E0283: faltam anotações de tipo quando vários tipos servem, por exemplo
Into::into(n)sem destino, ou uma função genérica chamada sem o turbofish. - E0599: nenhuma função encontrada.
Settings::default()dá esse erro seSettingsnão implementaDefault; acrescente#[derive(Default)]. Comlet s: Settings = Default::default();, o mesmo engano aparece como E0277. - E0424:
selfusado numa função sem parâmetroself. Também tem a ver com funções que pertencem a um tipo, e não a um valor específico. - Todos os erros do compilador: a lista completa de páginas de erro.
Teste o que aprendeu
Seção intitulada “Teste o que aprendeu”Teste o que aprendeu
1. let opts = Default::default(); dá E0790. Options deriva Default. Qual linha corrige?
2. Por que let s: Settings = Default::default(); compila, mas let n: String = Shape::name(); continua dando E0790?
3. Dentro de fn show<T: Shape>(), como você deve chamar a função de trait name()?
Pontuação: 0 / 3
Para saber mais
Seção intitulada “Para saber mais”- E0790 — Rust error codes index (em inglês): a explicação oficial deste erro, com um exemplo curto e a correção.
- Traits — The Rust Book (em inglês): o que é um trait, como implementar um e as implementações padrão.
- Advanced Traits — The Rust Book (em inglês): inclui a seção sobre sintaxe totalmente qualificada, com o exemplo clássico
<Dog as Animal>::baby_name(). - Disambiguating overlapping traits — Rust by Example (em inglês): um exemplo curto, para rodar, de como escolher entre dois traits que têm um método com o mesmo nome.
Default— documentação da biblioteca padrão (em inglês): como derivarDefault, implementar à mão e o padrão..Default::default().