Ir al contenido

Error E0790 de Rust: cannot call associated function on trait (falta el tipo)

E0790 significa que le pediste a un trait algo que solo puede darte un tipo. Default::default() a secas es como decir “dame el valor por defecto” sin decir el valor por defecto de qué. Es un error común, y la solución suele ser poner delante el nombre del tipo: Settings::default() en lugar de Default::default(). Esta página te muestra primero las soluciones y después explica la diferencia entre un trait y un tipo, para que este error deje de sorprenderte. Forma parte de la guía de errores del compilador.

error[E0790]: cannot call associated function on trait without specifying the corresponding `impl` type
--> src/main.rs:8:20
|
8 | let settings = Default::default(); // ❌ ¿el valor por defecto de qué 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(); // ❌ ¿el valor por defecto de qué tipo?
| +++++++++++++++++++ +

El mensaje quiere decir “no se puede llamar a una función asociada de un trait sin indicar el tipo del impl correspondiente”. El help propone usar una ruta completa (fully-qualified path) hacia una implementación concreta.

Este es el programa que lo produce:

#[derive(Debug, Default)]
struct Settings {
volume: u8,
dark_mode: bool,
}
fn main() {
let settings = Default::default(); // ❌ ¿el valor por defecto de qué tipo?
println!("{:?}", settings);
}

Te sale el mismo error cuando llamas a una función de tu propio trait a través del nombre del trait, como Shape::new(). Aquí rustc ve dos tipos que implementan Shape, así que te ofrece los dos:

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();
| ++++++++++ +

El help sugiere una de las implementaciones disponibles: la de Circle o la de Square.

Con una constante de un trait, como Plugin::ID, el titular cambia un poco: cannot refer to the associated constant on trait without specifying the corresponding `impl` type (no se puede usar la constante asociada de un trait sin indicar el tipo). Mismo problema, mismas soluciones.

Fíjate en lo que marca rustc:

  1. ^^^^ debajo de Default::default(): “cannot call associated function of trait” (no se puede llamar a la función asociada del trait). default es una función que pertenece al trait Default, y la llamaste a través del nombre del trait.
  2. El help sugiere </* self type */ as Default>::default(). La parte /* self type */ es un hueco que tienes que rellenar tú. rustc te pregunta: “¿el default de qué tipo quieres decir?”.
  3. En el ejemplo de Shape, rustc rellena el hueco por ti con cada tipo que encontró (Circle, Square). Si solo un tipo implementa el trait, sugiere únicamente ese.

Esta es la idea clave. Un trait es una promesa: “cualquier tipo que implemente Default sabe crear un valor inicial de sí mismo”. Un tipo es quien cumple la promesa: Settings, String, u32 y Vec<T> la cumplen cada uno a su manera. Default::default() le pide un valor a la propia promesa, pero una promesa no puede fabricar nada. Solo un tipo puede.

Piensa en un cartel de “comida para llevar”. Muchos restaurantes lo ponen en su ventana. Pero no puedes pedirle la cena al cartel; tienes que elegir un restaurante. Settings::default() es elegir el restaurante.

La mayoría de los métodos de un trait reciben self, como circle.area(). Esos nunca provocan E0790, porque el valor le dice a Rust el tipo: circle es un Circle, así que llama al area de Circle. Puedes leer más sobre esto en Traits.

Algunas funciones de un trait no tienen self: constructores como new() y default(), conversiones como From::from y constantes del trait como ID. Sin un valor que mirar, Rust solo tiene dos sitios donde averiguar el tipo: el nombre que escribes delante (Settings::) o el tipo que debe tener el resultado (let s: Settings = ...). Si no hay ninguno de los dos, Rust no puede adivinarlo. Hay muchísimos tipos que implementan Default, y escoger uno al azar cambiaría sin avisar lo que hace tu programa.

Es lo mismo que una interfaz en Java o C#. No puedes hacer new de una interfaz; necesitas una clase que la implemente. Rust simplemente te lo dice al compilar, y te señala el sitio exacto que hay que arreglar.

Elige esta casi siempre. Cambia el nombre del trait por el nombre del tipo. Es la solución más corta, y se lee bien: “el Settings por defecto”, “un Circle nuevo”:

#[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(); // en lugar de Default::default()
let shape = Circle::new(); // en lugar de Shape::new()
println!("{:?}", settings);
println!("Un {} con radio {}", Circle::name(), shape.radius);
}

Salida:

Settings { volume: 0, dark_mode: false }
Un círculo con radio 1
▶ Pruébalo tú mismo

Circle::new() funciona aunque new esté definida en el trait y no en un bloque impl Circle. Rust busca new en Circle, la encuentra a través de impl Shape for Circle y la llama. El trait tiene que estar en el ámbito (scope): definido en el mismo archivo, o importado con use.

Elige esta cuando te gusta el estilo Default::default(), o cuando el nombre del tipo es largo. Escribe el tipo en la variable, y Rust deduce a partir de él qué default llamar:

#[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);
}

Salida:

Settings { volume: 0, dark_mode: false }
42
▶ Pruébalo tú mismo

Esto solo funciona cuando la función devuelve el propio tipo (Self), como hacen default(), new() y from(). No funciona con fn name() -> String: saber que el resultado es un String no le dice a Rust de qué figura hablas. Para esas, usa la Solución 1 o la Solución 3. Más sobre anotaciones en Tipos de datos.

Solución 3: Sintaxis totalmente calificada <Type as Trait>::function()

Sección titulada «Solución 3: Sintaxis totalmente calificada <Type as Trait>::function()»

Esta es la forma de “decirlo todo” que sugiere rustc. Lee <Circle as Shape>::new() como “el new que tiene Circle porque implementa Shape”. Siempre funciona, pero Circle::new() se lee mejor, así que solo la necesitas cuando la forma corta es ambigua:

  • el tipo implementa dos traits con una función del mismo nombre, o
  • el tipo tiene su propia función con ese nombre, y tú quieres la versión del trait.

Aquí una venta (Sale) se puede escribir como fila de un CSV o como informe impreso, y los dos traits tienen un header(). Escribir Sale::header() da E0034 (en inglés), “multiple applicable items in scope” (varios elementos aplicables en el ámbito), así que dices cuál quieres:

trait Csv {
fn header() -> String;
}
trait Report {
fn header() -> String;
}
struct Sale;
impl Csv for Sale {
fn header() -> String { String::from("fecha,importe") }
}
impl Report for Sale {
fn header() -> String { String::from("Ventas del mes") }
}
fn main() {
println!("{}", <Sale as Csv>::header());
println!("{}", <Sale as Report>::header());
}

Salida:

fecha,importe
Ventas del mes
▶ Pruébalo tú mismo

La lección de Traits avanzados tiene otro ejemplo, también con métodos que reciben &self.

Solución 4: En código genérico, usa T:: y el turbofish

Sección titulada «Solución 4: En código genérico, usa T:: y el turbofish»

Elige esta cuando estás escribiendo una función genérica. Dentro de fn register<T: Plugin>(), el tipo es T, así que llama a T::name() y T::ID, no a Plugin::name(). Al llamar a la función, pasa el tipo con el turbofish ::<Type>, porque no hay ningún argumento del que Rust pueda deducirlo:

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 n.º {}: {}", T::ID, T::name())
}
fn main() {
println!("{}", register::<Logger>());
println!("{}", register::<Backup>());
}

Salida:

Plugin n.º 7: logger
Plugin n.º 12: backup
▶ Pruébalo tú mismo

Si te olvidas del turbofish y escribes register(), te sale E0283: “cannot infer type of the type parameter T” (no se puede deducir el tipo del parámetro T). Aquí T también es el tipo, así que T:: funciona igual que Circle:: en la Solución 1. Mira Genéricos para saber más sobre los parámetros de tipo.

Default::default() sin ninguna pista. let config = Default::default(); falla, pero la misma llamada funciona allí donde el tipo ya se conoce: en un campo de un struct, en un argumento de función o en la sintaxis de “rellena el resto”:

let settings = Settings { volume: 8, ..Default::default() }; // OK: Rust sabe que es Settings

Arregla la línea que falla con Settings::default() o con let config: Settings = Default::default();. Mira Traits comunes.

Dentro de una función genérica. Escribir el nombre del trait en lugar 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())
| ++++++++++ +

Esta vez el help propone la única implementación disponible. Cuidado: rustc sugiere <Circle as Shape> porque, de momento, Circle es la única figura. Eso compila, pero la función diría siempre “circle”, sea cual sea T. La solución correcta es T::name(), como en la Solución 4.

Constantes de traits. println!("{}", Plugin::ID); le pide un número a la promesa, y solo cada plugin tiene el suyo. Solución: Logger::ID, o T::ID en código genérico.

From::from(x) sin un destino. let total = From::from(42u8); da E0790, porque hay muchos tipos que se pueden crear a partir de un u8. Solución: u32::from(42u8), o let total: u32 = 42u8.into();. Mira Conversiones.

  • E0282: type annotations needed (hacen falta anotaciones de tipo). Si usas el valor después, como let c = Shape::new(); seguido de c.area(), rustc informa E0282 en su lugar. Sirven las mismas soluciones.
  • E0283: hacen falta anotaciones de tipo cuando encajan muchos tipos, por ejemplo Into::into(n) sin destino, o una función genérica llamada sin turbofish.
  • E0599: no se encuentra la función. Settings::default() da este error si Settings no implementa Default; añade #[derive(Default)]. Con let s: Settings = Default::default(); el mismo despiste aparece como E0277.
  • E0424: self usado en una función sin parámetro self. También trata de funciones que pertenecen a un tipo y no a un valor concreto.
  • Todos los errores del compilador: la lista completa de páginas de errores.

Comprueba lo aprendido

1. let opts = Default::default(); da E0790. Options deriva Default. ¿Qué línea lo arregla?

2. ¿Por qué let s: Settings = Default::default(); compila, pero let n: String = Shape::name(); sigue dando E0790?

3. Dentro de fn show<T: Shape>(), ¿cómo deberías llamar a la función name() del trait?

Puntuación: 0 / 3