Aller au contenu

Erreur Rust E0034 : multiple applicable items in scope (méthode ambiguë)

E0034 veut dire que votre type a deux méthodes du même nom, chacune venant d’un trait différent, et que vous l’avez appelée de la façon courte, comme book.describe(). Rust voit les deux et refuse de choisir à votre place. Rien n’est cassé dans vos types : vous devez juste dire de quel trait vous parlez. Cela tient en général en une ligne, et rustc l’écrit même pour vous. Cette page fait partie du guide des erreurs du compilateur Rust.

error[E0034]: multiple applicable items in scope
--> src/main.rs:28:25
|
28 | println!("{}", book.describe()); // ❌ celle de Summary ou celle de Label ?
| ^^^^^^^^ multiple `describe` found
|
note: candidate #1 is defined in an impl of the trait `Label` for the type `Book`
--> src/main.rs:21:5
|
21 | fn describe(&self) -> String {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: candidate #2 is defined in an impl of the trait `Summary` for the type `Book`
--> src/main.rs:15:5
|
15 | fn describe(&self) -> String {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: disambiguate the method for candidate #1
|
28 - println!("{}", book.describe()); // ❌ celle de Summary ou celle de Label ?
28 + println!("{}", Label::describe(&book)); // ❌ celle de Summary ou celle de Label ?
|
help: disambiguate the method for candidate #2
|
28 - println!("{}", book.describe()); // ❌ celle de Summary ou celle de Label ?
28 + println!("{}", Summary::describe(&book)); // ❌ celle de Summary ou celle de Label ?
|

Le titre veut dire « plusieurs éléments applicables dans la portée ». Les notes présentent chaque candidat, et chaque help: disambiguate the method montre comment lever l’ambiguïté pour ce candidat.

Voici le programme qui la provoque :

trait Summary {
fn describe(&self) -> String;
}
trait Label {
fn describe(&self) -> String;
}
struct Book {
title: String,
pages: u32,
}
impl Summary for Book {
fn describe(&self) -> String {
format!("{} ({} pages)", self.title, self.pages)
}
}
impl Label for Book {
fn describe(&self) -> String {
format!("LIVRE : {}", self.title.to_uppercase())
}
}
fn main() {
let book = Book { title: String::from("Rust pour les crabes"), pages: 320 };
println!("{}", book.describe()); // ❌ celle de Summary ou celle de Label ?
}

Lisez le message de haut en bas :

  1. Les ^^^^^^^^ sous describe : « multiple describe found » (plusieurs describe trouvées). Rust a cherché une méthode appelée describe sur Book et en a trouvé plus d’une.
  2. Les deux lignes note: listent les candidats : l’un vient de impl Label for Book (ligne 21), l’autre de impl Summary for Book (ligne 15). Les deux existent vraiment, les deux fonctionnent, et elles ne renvoient pas le même texte.
  3. Les deux lignes help: montrent la correction pour chaque choix. Summary::describe(&book) veut dire « appelle le describe qui vient de Summary, avec &book comme self ».

Imaginez deux collègues qui s’appellent tous les deux Sam dans le même bureau. Crier « Sam ! » ne marche pas ; vous dites « Sam de la compta ». Summary::describe, c’est « Sam de la compta ».

Un appel de méthode comme book.describe() est un raccourci. Rust regarde le type de book, puis cherche parmi ses propres méthodes et parmi les méthodes de chaque trait qui est dans la portée (in scope) et implémenté pour Book. D’habitude, cette recherche trouve exactement une méthode. Ici, elle en trouve deux, et elles ne font pas la même chose : l’une affiche un résumé, l’autre une étiquette de rayon.

Rust pourrait en choisir une selon une règle cachée, comme « le trait défini en premier ». Mais alors, ajouter un trait ou inverser deux lignes use changerait discrètement ce que votre programme affiche. Rust refuse de deviner et vous demande de le dire une fois, dans le code, là où tous les lecteurs peuvent le voir.

Il y a une exception : une méthode définie dans le bloc propre au type, impl Book, l’emporte toujours sur une méthode de trait du même nom. Ce cas ne donne donc jamais E0034. Pour en savoir plus, voir Les traits et Les traits avancés.

Solution 1 : l’appeler par le trait, Trait::method(&x)

Section intitulée « Solution 1 : l’appeler par le trait, Trait::method(&x) »

Choisissez-la quand vous avez besoin des deux méthodes et que vous voulez simplement dire laquelle à chaque fois. C’est exactement ce que propose rustc. La valeur devient le premier argument, avec le même & que celui qu’attend le &self de la méthode :

trait Summary {
fn describe(&self) -> String;
}
trait Label {
fn describe(&self) -> String;
}
struct Book {
title: String,
pages: u32,
}
impl Summary for Book {
fn describe(&self) -> String {
format!("{} ({} pages)", self.title, self.pages)
}
}
impl Label for Book {
fn describe(&self) -> String {
format!("LIVRE : {}", self.title.to_uppercase())
}
}
fn main() {
let book = Book { title: String::from("Rust pour les crabes"), pages: 320 };
println!("{}", Summary::describe(&book));
println!("{}", Label::describe(&book));
}

Sortie :

Rust pour les crabes (320 pages)
LIVRE : RUST POUR LES CRABES
▶ Essayez par vous-même

Si la méthode prend &mut self, passez &mut book. Si elle prend self, passez book (et la valeur est déplacée, exactement comme avec le point).

Solution 2 : la syntaxe entièrement qualifiée, <Type as Trait>::method()

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

Choisissez-la quand la fonction en conflit n’a pas de self, comme un constructeur ou un fn category(). Il n’y a alors aucune valeur que Rust pourrait regarder, et Trait::category() ne suffit pas : vous devez aussi nommer le type. <Book as Summary>::category() se lit « le category que Book possède parce qu’il implémente Summary ». Ça marche aussi pour les méthodes :

trait Summary {
fn category() -> String;
fn describe(&self) -> String;
}
trait Label {
fn category() -> String;
}
struct Book {
title: String,
}
impl Summary for Book {
fn category() -> String { String::from("Documentaire") }
fn describe(&self) -> String { format!("Un livre intitulé {}", self.title) }
}
impl Label for Book {
fn category() -> String { String::from("Rayon B") }
}
fn main() {
let book = Book { title: String::from("Rust pour les crabes") };
println!("{}", <Book as Summary>::category());
println!("{}", <Book as Label>::category());
println!("{}", <Book as Summary>::describe(&book));
}

Sortie :

Documentaire
Rayon B
Un livre intitulé Rust pour les crabes
▶ Essayez par vous-même

Écrire Book::category() ici donne aussi E0034, avec l’aide use fully-qualified syntax to disambiguate (utilisez la syntaxe entièrement qualifiée pour lever l’ambiguïté). Écrire seulement Summary::category() donne une autre erreur, E0790, car le trait ne sait toujours pas de quel type vous parlez.

Choisissez-la quand les deux traits sont à vous et que le nom commun est un accident. Deux méthodes qui font des choses différentes méritent des noms différents, et le raccourci book.describe() remarche ensuite. C’est souvent la solution la plus propre :

trait Summary {
fn describe(&self) -> String;
}
trait Label {
fn label_text(&self) -> String;
}
struct Book {
title: String,
pages: u32,
}
impl Summary for Book {
fn describe(&self) -> String { format!("{} ({} pages)", self.title, self.pages) }
}
impl Label for Book {
fn label_text(&self) -> String { format!("LIVRE : {}", self.title.to_uppercase()) }
}
fn main() {
let book = Book { title: String::from("Rust pour les crabes"), pages: 320 };
println!("{}", book.describe());
println!("{}", book.label_text());
}

Sortie :

Rust pour les crabes (320 pages)
LIVRE : RUST POUR LES CRABES
▶ Essayez par vous-même

Bien sûr, vous ne pouvez pas renommer les méthodes des traits d’une autre crate. Dans ce cas, utilisez la solution 1 ou la solution 4.

Solution 4 : n’importer que le trait dont vous avez besoin

Section intitulée « Solution 4 : n’importer que le trait dont vous avez besoin »

Choisissez-la quand les traits se trouvent dans des modules ou des crates différents. Les méthodes d’un trait ne s’utilisent avec le point que si le trait est dans la portée. Donc, si une partie de votre code n’a besoin que de l’un d’eux, n’importez que celui-là à cet endroit. Vous pouvez même mettre un use à l’intérieur d’une fonction, pour qu’il ne s’applique qu’à cette fonction :

mod report {
pub trait Summary {
fn describe(&self) -> String;
}
}
mod shop {
pub trait Label {
fn describe(&self) -> String;
}
}
struct Book {
title: String,
}
impl report::Summary for Book {
fn describe(&self) -> String { format!("Un livre intitulé {}", self.title) }
}
impl shop::Label for Book {
fn describe(&self) -> String { format!("LIVRE : {}", self.title.to_uppercase()) }
}
fn print_label(book: &Book) {
use shop::Label; // seul Label est dans la portée ici
println!("{}", book.describe());
}
fn main() {
use report::Summary; // seul Summary est dans la portée ici
let book = Book { title: String::from("Rust pour les crabes") };
println!("{}", book.describe());
print_label(&book);
}

Sortie :

Un livre intitulé Rust pour les crabes
LIVRE : RUST POUR LES CRABES
▶ Essayez par vous-même

Remarquez que les blocs impl écrivent report::Summary et shop::Label en entier. Implémenter un trait n’a pas besoin de use ; seul l’appel avec le point en a besoin. Voir Les modules pour en savoir plus sur use.

Votre propre to_string(). Tout type qui implémente Display reçoit déjà to_string() grâce au trait standard ToString. Ajoutez un trait à vous avec une méthode to_string, et point.to_string() donne E0034, avec candidate #2 is defined in an impl of the trait `ToString` for the type `T` . Correction : renommez la vôtre (pretty(), to_label()), ou appelez Pretty::to_string(&point).

Deux imports prelude::*. Les bibliothèques proposent souvent use some_crate::prelude::*; pour importer leurs traits. Importez deux preludes qui ajoutent tous les deux une méthode draw() ou render() au même type, et l’appel avec le point devient ambigu. Correction : remplacez l’un des imports globaux (*) par les seuls éléments que vous utilisez, ou appelez Render::draw(&data).

Les fonctions génériques avec deux contraintes. Dans fn show<T: Summary + Label>(item: &T), item.describe() donne E0034. Attention à la suggestion de rustc dans ce cas : elle propose Label::describe(&item), mais item est déjà une référence, ce qui donne E0277 (the trait bound `&T: Label` is not satisfied, &T n’implémente pas Label). Écrivez Label::describe(item), sans le & en trop. Voir Les génériques.

  • E0790 : vous avez appelé une fonction de trait sans self, comme Default::default() ou Summary::category(), sans dire de quel type il s’agit.
  • E0599 : le problème inverse, aucune méthode de ce nom n’a été trouvée. Souvent, le trait n’est pas importé.
  • E0277 : contrainte de trait non satisfaite, par exemple après un & en trop dans Trait::method(&x).
  • E0428 : le nom est défini plusieurs fois. Deux éléments du même nom dans un module, et non deux méthodes de trait.
  • Toutes les erreurs du compilateur : la liste complète des pages d’erreurs.

Petit quiz

1. Book implémente Summary et Label, et les deux ont fn describe(&self). Quelle ligne appelle la version de Label ?

2. Les deux traits ont fn category() -> String, sans self. Quel appel fonctionne ?

3. Book a son propre impl Book { fn describe(&self) ... } et implémente aussi Summary, qui a lui aussi describe. Que fait book.describe() ?

Score : 0 / 3