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.
Le message d’erreur
Section intitulée « Le message d’erreur »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.
Ce que ça veut dire
Section intitulée « Ce que ça veut dire »Regardez ce que rustc souligne :
- Les
^^^^sousDefault::default(): « cannot call associated function of trait » (impossible d’appeler la fonction associée du trait).defaultest une fonction qui appartient au traitDefault, et vous l’avez appelée par le nom du trait. - Le
helpsuggère</* self type */ as Default>::default(). La partie/* self type */est un blanc à remplir. Rustc vous demande : « la fonctiondefaultde quel type voulez-vous dire ? » - 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.
Pourquoi Rust fait ça
Section intitulée « Pourquoi Rust fait ça »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.
Comment corriger
Section intitulée « Comment corriger »Solution 1 : l’appeler sur le type
Section intitulée « Solution 1 : l’appeler sur le type »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 1Circle::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).
Solution 2 : annoter la variable
Section intitulée « Solution 2 : annoter la variable »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Ç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,montantVentes du moisLa 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 : loggerPlugin n°12 : backupSi 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.
Où vous le verrez souvent
Section intitulée « Où vous le verrez souvent »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 SettingsCorrigez 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.
Erreurs liées
Section intitulée « Erreurs liées »- E0282 : type annotations needed (annotations de type nécessaires). Si vous utilisez la valeur plus loin, comme
let c = Shape::new();suivi dec.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 siSettingsn’implémente pasDefault; ajoutez#[derive(Default)]. Aveclet s: Settings = Default::default();, la même erreur apparaît sous la forme E0277. - E0424 :
selfutilisé dans une fonction sans paramètreself. 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
Section intitulée « Petit quiz »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
Pour aller plus loin
Section intitulée « Pour aller plus loin »- E0790 — Rust error codes index (en anglais) : l’explication officielle de cette erreur, avec un court exemple et sa correction.
- Traits — The Rust Book (en anglais) : ce qu’est un trait, comment en implémenter un, et les implémentations par défaut.
- Advanced Traits — The Rust Book (en anglais) : avec la section sur la syntaxe entièrement qualifiée et l’exemple classique
<Dog as Animal>::baby_name(). - Disambiguating overlapping traits — Rust by Example (en anglais) : un court exemple exécutable pour choisir entre deux traits qui ont chacun une méthode du même nom.
Default— documentation de la bibliothèque standard (en anglais) : dériverDefault, l’implémenter à la main, et le pattern..Default::default().