Aller au contenu

Erreur Rust E0790 : cannot call associated function on trait (type manquant)

E0790 veut dire que vous avez demandé à un trait quelque chose que seul un type peut vous donner. Default::default() tout seul, c’est comme dire « donne-moi la valeur par défaut » sans préciser la valeur par défaut de quoi. C’est une erreur courante, et la correction consiste généralement à mettre le nom du type devant : Settings::default() au lieu de Default::default(). Cette page vous montre d’abord les solutions, puis vous explique la différence entre un trait et un type, pour que cette erreur ne vous surprenne plus. Elle fait partie du guide des erreurs du compilateur 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(); // ❌ the default of which type?
| ^^^^^^^^^^^^^^^^^^ 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(); // ❌ the default of which type?
| +++++++++++++++++++ +

Le compilateur vous dit : « impossible d’appeler une fonction associée sur un trait sans préciser le type de l’impl correspondant ». Le help propose d’utiliser un chemin entièrement qualifié vers une implémentation précise.

Voici le programme qui la provoque :

#[derive(Debug, Default)]
struct Settings {
volume: u8,
dark_mode: bool,
}
fn main() {
let settings = Default::default(); // ❌ the default of which type?
println!("{:?}", settings);
}

(Le commentaire demande : la valeur par défaut de quel type ?)

Vous obtenez la même erreur quand vous appelez la fonction de votre propre trait sur le nom du trait, comme Shape::new(). Ici, rustc voit deux types qui implémentent Shape, et il propose donc les deux :

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

Le help vous propose un chemin entièrement qualifié vers l’une des implémentations disponibles : Circle ou Square.

Pour une constante de trait comme Plugin::ID, la première ligne change un peu : cannot refer to the associated constant on trait without specifying the corresponding `impl` type (impossible de faire référence à la constante associée sans préciser le type). Même problème, mêmes solutions.

Regardez ce que rustc souligne :

  1. Les ^^^^ sous Default::default() : « cannot call associated function of trait » (impossible d’appeler la fonction associée du trait). default est une fonction qui appartient au trait Default, et vous l’avez appelée par le nom du trait.
  2. Le help suggère </* self type */ as Default>::default(). La partie /* self type */ est un blanc à remplir. Rustc vous demande : « la fonction default de quel type voulez-vous dire ? »
  3. Dans l’exemple de Shape, rustc remplit le blanc pour vous avec chaque type qu’il a trouvé (Circle, Square). Si un seul type implémente le trait, il ne propose que celui-là.

Voici l’idée clé. Un trait est une promesse : « tout type qui implémente Default sait fabriquer une valeur de départ de lui-même ». Un type est ce qui tient la promesse : Settings, String, u32 et Vec<T> la tiennent chacun à leur manière. Default::default() demande une valeur à la promesse elle-même, mais une promesse ne peut rien fabriquer. Seul un type le peut.

Pensez à un autocollant « vente à emporter » sur une vitrine. Beaucoup de restaurants en ont un. Mais vous ne pouvez pas commander votre dîner à l’autocollant : il faut choisir un restaurant. Settings::default(), c’est choisir le restaurant.

La plupart des méthodes de trait prennent self, comme circle.area(). Celles-là ne provoquent jamais E0790, car la valeur indique le type à Rust : circle est un Circle, donc Rust appelle le area de Circle. Pour en savoir plus, voir Les traits.

Certaines fonctions de trait n’ont pas de self : les constructeurs comme new() et default(), les conversions comme From::from, et les constantes de trait comme ID. Sans valeur à regarder, Rust n’a que deux endroits pour connaître le type : le nom que vous écrivez devant (Settings::), ou le type que le résultat doit avoir (let s: Settings = ...). Si aucun des deux n’est là, Rust ne peut pas deviner. Beaucoup de types implémentent Default, et en choisir un au hasard changerait discrètement ce que fait votre programme.

C’est la même chose qu’avec une interface en Java ou en C#. Vous ne pouvez pas faire new sur une interface ; il vous faut une classe qui l’implémente. Rust vous le signale simplement à la compilation, avec l’endroit exact à corriger.

Choisissez-la presque toujours. Remplacez le nom du trait par le nom du type. C’est la correction la plus courte, et elle se lit bien : « la valeur par défaut de Settings », « un nouveau 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("cercle")
}
}
fn main() {
let settings = Settings::default(); // au lieu de Default::default()
let shape = Circle::new(); // au lieu de Shape::new()
println!("{:?}", settings);
println!("Un {} de rayon {}", Circle::name(), shape.radius);
}

Sortie :

Settings { volume: 0, dark_mode: false }
Un cercle de rayon 1
▶ Essayez par vous-même

Circle::new() fonctionne même si new est défini dans le trait, et pas dans un bloc impl Circle. Rust cherche new sur Circle, le trouve grâce à impl Shape for Circle, et l’appelle. Le trait doit être dans la portée (défini dans le même fichier, ou importé avec use).

Choisissez-la si vous aimez le style Default::default(), ou si le nom du type est long. Écrivez le type sur la variable, et Rust en déduit quelle fonction default appeler :

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

Sortie :

Settings { volume: 0, dark_mode: false }
42
▶ Essayez par vous-même

Ça ne marche que si la fonction renvoie le type lui-même (Self), comme default(), new() et from(). Ça ne marche pas pour fn name() -> String : savoir que la réponse est une String ne dit rien à Rust sur la forme que vous voulez. Pour celles-là, utilisez la solution 1 ou la solution 3. Pour en savoir plus sur les annotations, voir Les types de données.

Solution 3 : la syntaxe entièrement qualifiée <Type as Trait>::function()

Section intitulée « Solution 3 : la syntaxe entièrement qualifiée <Type as Trait>::function() »

C’est la forme « je dis tout » que rustc suggère. Lisez <Circle as Shape>::new() comme « le new que Circle possède parce qu’il implémente Shape ». Elle marche toujours, mais Circle::new() est plus facile à lire ; vous n’en avez donc besoin que quand la forme courte est ambiguë :

  • le type implémente deux traits qui ont une fonction du même nom, ou
  • le type a sa propre fonction de ce nom, et vous voulez la version du trait.

Ici, une Sale (une vente) peut s’écrire comme une ligne CSV ou comme un rapport imprimé, et les deux traits ont un header(). Écrire Sale::header() donne E0034 (en anglais) « multiple applicable items in scope » (plusieurs éléments possibles dans la portée), donc vous précisez lequel :

trait Csv {
fn header() -> String;
}
trait Report {
fn header() -> String;
}
struct Sale;
impl Csv for Sale {
fn header() -> String { String::from("date,montant") }
}
impl Report for Sale {
fn header() -> String { String::from("Ventes du mois") }
}
fn main() {
println!("{}", <Sale as Csv>::header());
println!("{}", <Sale as Report>::header());
}

Sortie :

date,montant
Ventes du mois
▶ Essayez par vous-même

La leçon Les traits avancés en donne un autre exemple, avec aussi des méthodes qui prennent &self.

Solution 4 : dans le code générique, utiliser T:: et le turbofish

Section intitulée « Solution 4 : dans le code générique, utiliser T:: et le turbofish »

Choisissez-la quand vous écrivez une fonction générique. Dans fn register<T: Plugin>(), le type s’appelle T : appelez donc T::name() et T::ID, pas Plugin::name(). Quand vous appelez la fonction, passez le type avec le turbofish ::<Type>, car aucun argument ne permet à Rust de le deviner :

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

Sortie :

Plugin n°7 : logger
Plugin n°12 : backup
▶ Essayez par vous-même

Si vous oubliez le turbofish et écrivez register(), vous obtenez E0283 : « cannot infer type of the type parameter T » (impossible de déduire le type du paramètre de type T). Ici aussi, T est le type, donc T:: fonctionne exactement comme Circle:: dans la solution 1. Voir Les génériques pour en savoir plus sur les paramètres de type.

Default::default() sans aucun indice. let config = Default::default(); échoue, mais le même appel marche partout où le type est déjà connu : dans un champ de struct, un argument de fonction, ou la syntaxe « remplis le reste » :

let settings = Settings { volume: 8, ..Default::default() }; // OK : Rust sait que c'est un Settings

Corrigez la ligne qui échoue avec Settings::default() ou let config: Settings = Default::default();. Voir Les traits courants.

Dans une fonction générique. Vous écrivez le nom du trait au lieu 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())
| ++++++++++ +

Le help propose le chemin entièrement qualifié vers la seule implémentation disponible. Attention : rustc suggère <Circle as Shape> parce que Circle est la seule forme pour l’instant. Ça compile, mais la fonction donnerait alors toujours le nom du cercle, quel que soit T. La bonne correction est T::name(), comme dans la solution 4.

Les constantes de trait. println!("{}", Plugin::ID); demande un nombre à la promesse, alors que seul chaque plugin en possède un. Correction : Logger::ID, ou T::ID dans le code générique.

From::from(x) sans type cible. let total = From::from(42u8); donne E0790, car beaucoup de types peuvent être construits à partir d’un u8. Correction : u32::from(42u8), ou let total: u32 = 42u8.into();. Voir Les conversions.

  • E0282 : type annotations needed (annotations de type nécessaires). Si vous utilisez la valeur plus loin, comme let c = Shape::new(); suivi de c.area(), rustc signale E0282 à la place. Les mêmes solutions fonctionnent.
  • E0283 : annotations de type nécessaires quand beaucoup de types conviennent, par exemple Into::into(n) sans type cible, ou une fonction générique appelée sans turbofish.
  • E0599 : aucune fonction trouvée. Settings::default() donne cette erreur si Settings n’implémente pas Default ; ajoutez #[derive(Default)]. Avec let s: Settings = Default::default();, la même erreur apparaît sous la forme E0277.
  • E0424 : self utilisé dans une fonction sans paramètre self. Là aussi, il s’agit de fonctions qui appartiennent à un type plutôt qu’à une valeur.
  • Toutes les erreurs du compilateur : la liste complète des pages d’erreurs.

Petit quiz

1. let opts = Default::default(); donne E0790. Options dérive Default. Quelle ligne corrige l'erreur ?

2. Pourquoi let s: Settings = Default::default(); compile, alors que let n: String = Shape::name(); donne encore E0790 ?

3. Dans fn show<T: Shape>(), comment appeler la fonction de trait name() ?

Score : 0 / 3