Pular para o conteúdo

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.

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.

Veja o que o rustc marca:

  1. ^^^^ embaixo de Default::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 trait Default, e você a chamou pelo nome do trait.
  2. A ajuda sugere </* self type */ as Default>::default(). A parte /* self type */ é um espaço em branco para você preencher. O rustc está perguntando: “o default de qual tipo você quer?”
  3. 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.

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.

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 1
▶ Experimente você mesmo

Circle::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).

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 }
42
▶ Experimente você mesmo

Isso 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,amount
Vendas do mês
▶ Experimente você mesmo

A 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: logger
Plugin #12: backup
▶ Experimente você mesmo

Se 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.

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 é Settings

Corrija 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.

  • E0282: type annotations needed (faltam anotações de tipo). Se você usa o valor depois, como let c = Shape::new(); seguido de c.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 se Settings não implementa Default; acrescente #[derive(Default)]. Com let s: Settings = Default::default();, o mesmo engano aparece como E0277.
  • E0424: self usado numa função sem parâmetro self. 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

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