Erreur Rust E0369 : binary operation cannot be applied to type
E0369 veut dire que vous avez utilisé un opérateur comme ==, < ou + sur un type qui ne sait pas encore s’en servir. En Rust, les opérateurs ne sont pas intégrés d’office à vos propres structs et enums. Chacun vient d’un trait (une capacité qu’un type déclare avoir), et votre type ne l’obtient que si vous dérivez ou implémentez ce trait. La correction tient le plus souvent en une ligne #[derive(...)], et le compilateur vous dit même laquelle. Pour les autres erreurs de cette série, consultez les erreurs du compilateur Rust.
Le message d’erreur
Section intitulée « Le message d’erreur »error[E0369]: binary operation `==` cannot be applied to type `Point` --> src/main.rs:9:10 |9 | if a == b { // ❌ Point ne sait pas se comparer | - ^^ - Point | | | Point |note: an implementation of `PartialEq` might be missing for `Point` --> src/main.rs:1:1 |1 | struct Point { | ^^^^^^^^^^^^ must implement `PartialEq`help: consider annotating `Point` with `#[derive(PartialEq)]` |1 + #[derive(PartialEq)]2 | struct Point { |Le compilateur dit : « l’opération binaire == ne peut pas être appliquée au type Point ». Il ajoute qu’il manque peut-être une implémentation de PartialEq, et propose d’ajouter #[derive(PartialEq)].
Voici le programme qui la provoque :
struct Point { x: i32, y: i32,}
fn main() { let a = Point { x: 1, y: 2 }; let b = Point { x: 1, y: 2 }; if a == b { // ❌ Point ne sait pas se comparer println!("Same point!"); }}La première ligne change selon l’opérateur, mais c’est la même erreur :
binary operation `<` cannot be applied to type `Version`: il manquePartialOrd(pareil pour>,<=,>=).cannot add `Point` to `Point`: il manqueAdd. Vous verrez aussi « cannot subtract », « cannot multiply », et ainsi de suite.cannot add `&str` to `&str`: vous avez essayé de coller deux tranches de chaîne (&str) avec+.
Ce que ça veut dire
Section intitulée « Ce que ça veut dire »Les étiquettes sous l’opérateur indiquent le type de chaque côté, ici Point et Point. Ensuite, la note: montre votre struct et dit qu’elle doit implémenter PartialEq (« must implement PartialEq »). Autrement dit : « vous me demandez de comparer deux Point, mais personne ne m’a dit ce que veut dire égal pour un Point ».
Imaginez un arbitre à qui l’on tend deux objets en demandant « lequel est le plus grand ? ». Pour des nombres, la réponse est évidente. Pour deux sacs à dos, l’arbitre a d’abord besoin d’une règle : au poids ? au volume ? Rust est cet arbitre prudent.
Pourquoi Rust fait ça
Section intitulée « Pourquoi Rust fait ça »Chaque opérateur est un appel de méthode déguisé. a == b appelle PartialEq::eq, a < b appelle PartialOrd::partial_cmp, et a + b appelle Add::add. Les nombres, String et beaucoup de types standard implémentent déjà ces traits. Votre nouvelle struct, elle, n’en a aucun au départ.
Rust ne devine pas, parce qu’il se tromperait parfois. Deux User sont-ils égaux quand leurs identifiants sont les mêmes, ou seulement quand tous les champs sont identiques ? Point + Point doit-il additionner les coordonnées, et est-ce que ça a seulement un sens d’additionner des points ? C’est vous qui décidez, en dérivant le trait (le comportement par défaut, champ par champ) ou en écrivant votre propre impl. Les traits sont expliqués dans Les traits courants.
Comment corriger
Section intitulée « Comment corriger »Solution 1 : #[derive(PartialEq)] pour == et !=
Section intitulée « Solution 1 : #[derive(PartialEq)] pour == et != »Choisissez cette solution pour == et != quand « égal » veut dire « tous les champs sont égaux ». C’est la bonne correction la plupart du temps, et c’est exactement ce que propose rustc. Ajouter aussi Debug vous permet d’afficher la struct :
#[derive(Debug, PartialEq)]struct Point { x: i32, y: i32,}
fn main() { let a = Point { x: 1, y: 2 }; let b = Point { x: 1, y: 2 }; let c = Point { x: 5, y: 0 }; if a == b { println!("Même point !"); } println!("{:?} != {:?} : {}", a, c, a != c);}Sortie :
Même point !Point { x: 1, y: 2 } != Point { x: 5, y: 0 } : trueChaque champ doit lui aussi pouvoir être comparé. Si le type d’un champ n’a pas PartialEq, dérivez-le aussi sur ce type. Les enums fonctionnent de la même façon : #[derive(PartialEq)] sur enum Status permet à status == Status::Banned de compiler.
Solution 2 : #[derive(PartialOrd)] pour < et >
Section intitulée « Solution 2 : #[derive(PartialOrd)] pour < et > »Choisissez cette solution quand votre type a un ordre naturel. PartialOrd a besoin de PartialEq, donc rustc propose #[derive(PartialEq, PartialOrd)]. La version dérivée compare les champs de haut en bas : d’abord major, et seulement s’ils sont égaux, minor. L’ordre des champs dans votre struct compte donc :
#[derive(Debug, PartialEq, PartialOrd)]struct Version { major: u32, minor: u32,}
fn main() { let installed = Version { major: 1, minor: 4 }; let required = Version { major: 1, minor: 7 }; if installed < required { println!("Veuillez mettre à jour depuis {:?}", installed); }
let next = Version { major: 2, minor: 0 }; println!("2.0 est plus récente que 1.7 : {}", next > required);}Sortie :
Veuillez mettre à jour depuis Version { major: 1, minor: 4 }2.0 est plus récente que 1.7 : trueSi vous voulez trier un Vec de votre type avec .sort(), il vous faudra aussi Eq et Ord. Voir Le tri.
Solution 3 : impl std::ops::Add pour +
Section intitulée « Solution 3 : impl std::ops::Add pour + »Choisissez cette solution quand additionner deux valeurs de votre type a un sens clair, comme additionner des coordonnées ou de l’argent. Il n’existe pas de derive pour les opérateurs mathématiques, donc vous écrivez un petit impl. Output est le type que renvoie le + :
use std::ops::Add;
#[derive(Debug, Clone, Copy, PartialEq)]struct Point { x: i32, y: i32,}
impl Add for Point { type Output = Point;
fn add(self, other: Point) -> Point { Point { x: self.x + other.x, y: self.y + other.y } }}
fn main() { let start = Point { x: 1, y: 2 }; let step = Point { x: 3, y: 4 }; let end = start + step; println!("{:?}", end); println!("{}", end == Point { x: 4, y: 6 });}Sortie :
Point { x: 4, y: 6 }trueSub, Mul, Div et Neg fonctionnent de la même façon, et se trouvent tous dans std::ops. Dériver Copy ici permet de continuer à utiliser start et step après le +. Plus de détails dans Les traits courants et Les traits avancés.
Solution 4 : coller des chaînes avec + ou format!
Section intitulée « Solution 4 : coller des chaînes avec + ou format! »Choisissez cette solution quand l’erreur dit cannot add `&str` to `&str`. + sur des chaînes demande une String possédée à gauche et un &str à droite. L’aide de rustc le dit à sa manière : « create an owned String from a string reference » (créez une String possédée à partir d’une référence de chaîne), donc first.to_owned() + second. Avec deux String, empruntez celle de droite : s1 + &s2. Et format! marche toujours, sans rien déplacer :
fn main() { let first = String::from("Bonjour, "); let second = String::from("Ferris");
let shout = format!("{first}{second} !"); // emprunte les deux let joined = first + &second; // first est déplacée dans joined let short = "Salut, ".to_owned() + "crabe"; // &str à gauche : on le rend possédé d'abord
println!("{shout}"); println!("{joined}"); println!("{short}"); println!("{second} est toujours là");}Sortie :
Bonjour, Ferris !Bonjour, FerrisSalut, crabeFerris est toujours là+ prend l’ownership (la possession) de la String de gauche et lui ajoute la suite, c’est pourquoi first n’est plus utilisable après cette ligne. Si ça vous embrouille, utilisez simplement format!. Voir Les chaînes de caractères.
Où vous le verrez souvent
Section intitulée « Où vous le verrez souvent »Comparer dans du code générique. La fonction classique largest échoue avec binary operation `>` cannot be applied to type `&T`, parce que T pourrait être n’importe quel type, même un type sans ordre :
fn largest<T>(list: &[T]) -> &T { let mut largest = &list[0]; for item in list { if item > largest { // ❌ T n'est peut-être pas comparable largest = item; } } largest}Correction : ajoutez une contrainte de trait, exactement comme le propose rustc : fn largest<T: PartialOrd>(list: &[T]) -> &T. Voir Les génériques.
Tester la valeur d’un enum. if status == Status::Banned échoue tant que l’enum n’a pas #[derive(PartialEq)]. Si vous ne pouvez pas modifier l’enum (il vient d’un autre crate), utilisez matches!(status, Status::Banned) ou un match à la place.
Coller deux String. first + second avec deux String, ce n’est pas E0369 : c’est E0308 (« expected &str, found String ») avec help: consider borrowing here, c’est-à-dire first + &second. Même famille, même correction que la solution 4.
Comparer deux types différents. count == limit avec un i32 et un f64 donne E0308, et name == 'F' avec une String et un char donne E0277 (« can’t compare String with char »). Un repère simple : vous obtenez E0369 quand le type de gauche n’a aucune version de l’opérateur, et E0308 ou E0277 quand il en a une, mais pas pour ce type de droite. Correction : convertissez un des deux côtés (count as f64 == limit, name == "F").
Erreurs liées
Section intitulée « Erreurs liées »- E0277 : trait bound not satisfied : l’opérateur existe pour le type de gauche, mais pas avec ce type de droite (
i32 + f64,String == char). - E0308 : mismatched types : ce que vous obtenez pour
String + Stringou en comparant deux structs différentes. - E0599 : no method found :
.sort()ou.contains()peuvent échouer de la même façon quand il manqueOrdouPartialEqà votre type. E0368: binary assignment operation += cannot be applied: la version+=de cette erreur. ImplémentezAddAssigndestd::ops.- Toutes les erreurs du compilateur : la liste complète des pages d’erreurs.
Petit quiz
Section intitulée « Petit quiz »Petit quiz
1. Vous écrivez if order_a == order_b pour une struct Order et obtenez E0369. Quelle est la correction habituelle ?
2. Avec #[derive(PartialOrd)] sur struct Version { major: u32, minor: u32 }, comment deux versions sont-elles comparées ?
3. first et second sont tous les deux des &str. Quelle expression les colle sans erreur ?
Score : 0 / 3
Pour aller plus loin
Section intitulée « Pour aller plus loin »- E0369 — Rust error codes index (en anglais) : l’explication officielle de cette erreur.
- Derivable Traits — The Rust Book, Appendix C (en anglais) : ce que vous apporte la dérivation de
PartialEqetPartialOrd, y compris l’ordre champ par champ. - Storing UTF-8 Encoded Text with Strings — The Rust Book (en anglais) : pourquoi
+sur des chaînes prend uneStringà gauche et un&strà droite, et quand utiliserformat!. - Operator Overloading — Rust by Example (en anglais) : un court exemple exécutable qui implémente
Addpour vos propres types. std::ops::Add— documentation de la bibliothèque standard (en anglais) : le traitAdd, son typeOutput, et l’addition de valeurs de types différents.