Zum Inhalt springen

Rust-Fehler E0790: cannot call associated function on trait (Typ fehlt)

E0790 bedeutet: Du hast einen Trait um etwas gebeten, das nur ein Typ liefern kann. Default::default() allein ist so, als würdest du sagen „Gib mir den Standardwert“, ohne zu sagen, wovon. Der Fehler ist häufig, und die Lösung ist meistens, den Namen des Typs davorzuschreiben: Settings::default() statt Default::default(). Diese Seite zeigt dir zuerst die Lösungen und erklärt dann den Unterschied zwischen einem Trait und einem Typ – damit dich dieser Fehler nicht mehr überrascht. Sie gehört zur Reihe Rust-Compilerfehler einfach erklärt.

error[E0790]: cannot call associated function on trait without specifying the corresponding `impl` type
--> src/main.rs:8:20
|
8 | let settings = Default::default(); // ❌ der Standardwert welches Typs?
| ^^^^^^^^^^^^^^^^^^ 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(); // ❌ der Standardwert welches Typs?
| +++++++++++++++++++ +

Auf Deutsch heißt das: „Eine assoziierte Funktion eines Traits lässt sich nicht aufrufen, ohne den passenden impl-Typ anzugeben.“ Das help schlägt einen vollständig qualifizierten Pfad zu einer konkreten Implementierung vor. Dieses Programm erzeugt die Meldung:

#[derive(Debug, Default)]
struct Settings {
volume: u8,
dark_mode: bool,
}
fn main() {
let settings = Default::default(); // ❌ der Standardwert welches Typs?
println!("{:?}", settings);
}

Denselben Fehler bekommst du, wenn du die Funktion deines eigenen Traits über den Trait-Namen aufrufst, etwa Shape::new(). Hier sieht rustc zwei Typen, die Shape implementieren, und bietet dir beide an:

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

Bei einer Konstante eines Traits wie Plugin::ID ändert sich die erste Zeile leicht: cannot refer to the associated constant on trait without specifying the corresponding `impl` type („auf die assoziierte Konstante eines Traits kann man nicht verweisen, ohne den impl-Typ anzugeben“). Gleiches Problem, gleiche Lösungen.

Schau dir an, was rustc markiert:

  1. ^^^^ unter Default::default(): „cannot call associated function of trait“ (eine assoziierte Funktion eines Traits kann nicht aufgerufen werden). default ist eine Funktion, die zum Trait Default gehört, und du hast sie über den Namen des Traits aufgerufen.
  2. Das help schlägt </* self type */ as Default>::default() vor. Der Teil /* self type */ ist eine Lücke, die du ausfüllen sollst. rustc fragt: „Das default welches Typs meinst du?“
  3. Im Shape-Beispiel füllt rustc die Lücke für dich aus, mit jedem gefundenen Typ (Circle, Square). Implementiert nur ein Typ den Trait, schlägt rustc nur diesen einen vor.

Hier die zentrale Idee. Ein Trait ist ein Versprechen: „Jeder Typ, der Default implementiert, kann einen Startwert von sich selbst erzeugen.“ Ein Typ ist das, was das Versprechen einhält: Settings, String, u32 und Vec<T> halten es jeweils auf ihre eigene Weise. Default::default() bittet das Versprechen selbst um einen Wert. Aber ein Versprechen kann nichts erzeugen. Das kann nur ein Typ.

Denk an ein Schild „Auch zum Mitnehmen“. Viele Restaurants hängen es ins Fenster. Aber beim Schild kannst du kein Abendessen bestellen. Du musst dir ein Restaurant aussuchen. Settings::default() ist das Aussuchen des Restaurants.

Die meisten Trait-Methoden nehmen self, wie circle.area(). Die verursachen nie E0790, denn der Wert verrät Rust den Typ: circle ist ein Circle, also wird das area von Circle aufgerufen. Mehr dazu findest du unter Traits.

Manche Trait-Funktionen haben kein self: Konstruktoren wie new() und default(), Konvertierungen wie From::from und Trait-Konstanten wie ID. Ohne einen Wert, den Rust ansehen kann, bleiben nur zwei Stellen, an denen Rust den Typ erfährt: der Name, den du davorschreibst (Settings::), oder der Typ, den das Ergebnis haben muss (let s: Settings = ...). Fehlt beides, kann Rust nicht raten. Viele Typen implementieren Default, und einen zufällig auszuwählen würde stillschweigend ändern, was dein Programm tut.

Das ist wie bei einem Interface in Java oder C#. Auf einem Interface kannst du kein new aufrufen, du brauchst eine Klasse, die es implementiert. Rust sagt es dir nur schon beim Kompilieren – mit der genauen Stelle, an der du es beheben musst.

Nimm diese Lösung fast immer. Ersetze den Namen des Traits durch den Namen des Typs. Das ist die kürzeste Lösung, und sie liest sich gut: „das Standard-Settings“, „ein neuer Circle“:

#[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("Kreis")
}
}
fn main() {
let settings = Settings::default(); // statt Default::default()
let shape = Circle::new(); // statt Shape::new()
println!("{:?}", settings);
println!("Ein {} mit Radius {}", Circle::name(), shape.radius);
}

Ausgabe:

Settings { volume: 0, dark_mode: false }
Ein Kreis mit Radius 1
▶ Selbst ausprobieren

Circle::new() funktioniert, obwohl new im Trait definiert ist und nicht in einem impl Circle-Block. Rust sucht new bei Circle, findet es über impl Shape for Circle und ruft es auf. Der Trait muss dafür im Gültigkeitsbereich (Scope) sein: in derselben Datei definiert oder mit use eingebunden.

Nimm diese Lösung, wenn du den Stil Default::default() magst oder der Typname lang ist. Schreib den Typ an die Variable, und Rust leitet daraus ab, welches default gemeint ist:

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

Ausgabe:

Settings { volume: 0, dark_mode: false }
42
▶ Selbst ausprobieren

Das klappt nur, wenn die Funktion den Typ selbst zurückgibt (Self), wie es default(), new() und from() tun. Bei fn name() -> String klappt es nicht: Dass das Ergebnis ein String ist, verrät Rust nicht, welche Form du meinst. Nimm dafür Lösung 1 oder Lösung 3. Mehr über Annotationen findest du unter Datentypen.

Lösung 3: Vollständig qualifizierte Syntax <Type as Trait>::function()

Abschnitt betitelt „Lösung 3: Vollständig qualifizierte Syntax <Type as Trait>::function()“

Das ist die Form „alles ausschreiben“, die rustc vorschlägt. Lies <Circle as Shape>::new() als „das new, das Circle hat, weil es Shape implementiert“. Diese Form funktioniert immer. Circle::new() ist aber leichter zu lesen, deshalb brauchst du sie nur, wenn die Kurzform mehrdeutig ist:

  • der Typ implementiert zwei Traits mit einer gleichnamigen Funktion, oder
  • der Typ hat eine eigene Funktion mit diesem Namen, und du willst die Version des Traits.

Hier lässt sich ein Sale als CSV-Zeile oder als gedruckter Bericht ausgeben, und beide Traits haben ein header(). Sale::header() liefert E0034 (auf Englisch) „multiple applicable items in scope“ (mehrere passende Elemente im Gültigkeitsbereich). Also sagst du, welches du meinst:

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("Monatsumsätze") }
}
fn main() {
println!("{}", <Sale as Csv>::header());
println!("{}", <Sale as Report>::header());
}

Ausgabe:

date,amount
Monatsumsätze
▶ Selbst ausprobieren

Die Lektion Fortgeschrittene Traits hat ein weiteres Beispiel, auch mit Methoden, die &self nehmen.

Lösung 4: In generischem Code T:: und den Turbofish benutzen

Abschnitt betitelt „Lösung 4: In generischem Code T:: und den Turbofish benutzen“

Nimm diese Lösung, wenn du eine generische Funktion schreibst. Innerhalb von fn register<T: Plugin>() ist der Typ T. Ruf also T::name() und T::ID auf, nicht Plugin::name(). Beim Aufruf der Funktion übergibst du den Typ mit dem Turbofish ::<Type>, denn es gibt kein Argument, aus dem Rust ihn ablesen könnte:

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

Ausgabe:

Plugin #7: logger
Plugin #12: backup
▶ Selbst ausprobieren

Lässt du den Turbofish weg und schreibst register(), bekommst du E0283: „cannot infer type of the type parameter T“ (der Typ des Typparameters T lässt sich nicht ableiten). Auch T ist hier ein Typ, deshalb funktioniert T:: genauso wie Circle:: in Lösung 1. Mehr über Typparameter findest du unter Generics.

Default::default() ohne jeden Anhaltspunkt. let config = Default::default(); schlägt fehl. Derselbe Aufruf funktioniert aber überall, wo der Typ schon bekannt ist: in einem Feld eines Structs, als Funktionsargument oder in der „Rest auffüllen“-Syntax:

let settings = Settings { volume: 8, ..Default::default() }; // OK: Rust weiß, dass es Settings ist

Behebe die fehlerhafte Zeile mit Settings::default() oder let config: Settings = Default::default();. Siehe Wichtige Traits.

In einer generischen Funktion. Du schreibst den Namen des Traits statt 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())
| ++++++++++ +

Vorsicht: rustc schlägt <Circle as Shape> vor, weil Circle bisher die einzige Form ist. Das kompiliert, aber die Funktion würde dann immer den Namen des Kreises liefern, egal was T ist. Die richtige Lösung ist T::name(), wie in Lösung 4.

Trait-Konstanten. println!("{}", Plugin::ID); bittet das Versprechen um eine Zahl, aber nur jedes einzelne Plugin hat eine. Lösung: Logger::ID, oder T::ID in generischem Code.

From::from(x) ohne Ziel. let total = From::from(42u8); liefert E0790, weil sich viele Typen aus einem u8 erzeugen lassen. Lösung: u32::from(42u8) oder let total: u32 = 42u8.into();. Siehe Konvertierungen.

  • E0282: type annotations needed (Typannotationen nötig). Benutzt du den Wert später, etwa let c = Shape::new(); gefolgt von c.area(), meldet rustc stattdessen E0282. Es helfen dieselben Lösungen.
  • E0283: Typannotationen nötig, weil viele Typen passen – zum Beispiel Into::into(n) ohne Ziel oder eine generische Funktion ohne Turbofish.
  • E0599: keine Funktion gefunden. Settings::default() liefert diesen Fehler, wenn Settings Default nicht implementiert. Ergänze #[derive(Default)]. Mit let s: Settings = Default::default(); zeigt sich derselbe Fehler als E0277.
  • E0424: self in einer Funktion ohne self-Parameter. Auch hier geht es um Funktionen, die zu einem Typ gehören statt zu einem einzelnen Wert.
  • Alle Compilerfehler: die vollständige Liste der Fehlerseiten.

Kurzer Check

1. let opts = Default::default(); liefert E0790. Options leitet Default ab. Welche Zeile behebt den Fehler?

2. Warum kompiliert let s: Settings = Default::default();, während let n: String = Shape::name(); immer noch E0790 liefert?

3. Wie solltest du innerhalb von fn show<T: Shape>() die Trait-Funktion name() aufrufen?

Punkte: 0 / 3