Error E0369 de Rust: binary operation cannot be applied to type
E0369 significa que usaste un operador como ==, < o + con un tipo que todavía no sabe hacerlo. En Rust, los operadores no vienen incluidos en tus propias structs y enums. Cada uno sale de un trait (rasgo), y tu tipo solo lo tiene cuando derivas o implementas ese trait. Normalmente la solución es una sola línea #[derive(...)], y el compilador incluso te dice cuál. Para ver los demás errores de esta serie, mira Errores del compilador de Rust.
El mensaje de error
Sección titulada «El mensaje de error»error[E0369]: binary operation `==` cannot be applied to type `Point` --> src/main.rs:9:10 |9 | if a == b { // ❌ Point no sabe compararse | - ^^ - 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 { |El compilador dice que la operación binaria == no se puede aplicar al tipo Point, que quizá falte una implementación de PartialEq, y te sugiere añadir #[derive(PartialEq)].
Este es el programa que lo provoca:
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 no sabe compararse println!("Same point!"); }}La primera línea cambia según el operador, pero es el mismo error:
binary operation `<` cannot be applied to type `Version`: faltaPartialOrd(también para>,<=,>=).cannot add `Point` to `Point`(no se puede sumarPointaPoint): faltaAdd. También verás “cannot subtract”, “cannot multiply”, etc.cannot add `&str` to `&str`: intentaste unir dos slices de cadena con+.
Qué significa
Sección titulada «Qué significa»Las etiquetas bajo el operador muestran el tipo de cada lado, aquí Point y Point. Después, la note: señala tu struct y dice que debe implementar PartialEq (“must implement PartialEq”). Dicho de otro modo: “me pediste comparar dos Point, pero nadie me dijo qué significa igual para un Point”.
Piensa en un árbitro al que le dan dos objetos y le preguntan “¿cuál es más grande?”. Con números la respuesta es obvia. Con dos mochilas, el árbitro necesita antes una regla: ¿por peso?, ¿por volumen? Rust es ese árbitro cuidadoso.
Por qué Rust hace esto
Sección titulada «Por qué Rust hace esto»Cada operador es una llamada a un método disfrazada. a == b llama a PartialEq::eq, a < b llama a PartialOrd::partial_cmp y a + b llama a Add::add. Los números, String y muchos tipos estándar ya implementan estos traits. Tu struct nueva empieza sin ninguno.
Rust no adivina, porque a veces adivinaría mal. ¿Dos User son iguales cuando coinciden sus IDs, o solo cuando coinciden todos los campos? ¿Point + Point debe sumar las coordenadas, o ni siquiera tiene sentido sumar puntos? Lo decides tú, derivando el trait (la opción por defecto, campo por campo) o escribiendo tu propio impl. Los traits se explican en la página de Traits comunes.
Cómo solucionarlo
Sección titulada «Cómo solucionarlo»Solución 1: #[derive(PartialEq)] para == y !=
Sección titulada «Solución 1: #[derive(PartialEq)] para == y !=»Elige esta para == y != cuando “igual” significa “todos los campos son iguales”. Es la solución correcta casi siempre, y es justo lo que sugiere rustc. Si además añades Debug, puedes imprimir 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!("¡Es el mismo punto!"); } println!("{:?} != {:?}: {}", a, c, a != c);}Salida:
¡Es el mismo punto!Point { x: 1, y: 2 } != Point { x: 5, y: 0 }: trueTodos los campos también tienen que poder compararse. Si el tipo de un campo no tiene PartialEq, derívalo también en ese tipo. Con los enums funciona igual: #[derive(PartialEq)] en enum Status hace que status == Status::Banned compile.
Solución 2: #[derive(PartialOrd)] para < y >
Sección titulada «Solución 2: #[derive(PartialOrd)] para < y >»Elige esta cuando tu tipo tiene un orden natural. PartialOrd necesita PartialEq, así que rustc sugiere #[derive(PartialEq, PartialOrd)]. La versión derivada compara los campos de arriba abajo: primero major y, solo si son iguales, minor. Así que el orden de los campos en tu struct importa:
#[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!("Actualiza desde {:?}, por favor", installed); }
let next = Version { major: 2, minor: 0 }; println!("2.0 es más nueva que 1.7: {}", next > required);}Salida:
Actualiza desde Version { major: 1, minor: 4 }, por favor2.0 es más nueva que 1.7: trueSi quieres usar .sort() en un Vec de tu tipo, también necesitarás Eq y Ord. Mira Ordenar y buscar.
Solución 3: impl std::ops::Add para +
Sección titulada «Solución 3: impl std::ops::Add para +»Elige esta cuando sumar dos valores de tu tipo tiene un significado claro, como sumar coordenadas o dinero. No hay derive para los operadores matemáticos, así que escribes un impl corto. Output es el tipo que devuelve el +:
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 });}Salida:
Point { x: 4, y: 6 }trueSub, Mul, Div y Neg funcionan igual, y todos están en std::ops. Derivar Copy aquí hace que start y step sigan disponibles después del +. Más en Traits comunes y Traits avanzados.
Solución 4: Unir cadenas con + o con format!
Sección titulada «Solución 4: Unir cadenas con + o con format!»Elige esta cuando el error dice cannot add `&str` to `&str`. El + con cadenas necesita un String con dueño a la izquierda y un &str a la derecha. La ayuda de rustc lo dice a su manera: “create an owned String from a string reference” (crea un String con dueño a partir de una referencia), es decir, first.to_owned() + second. Con dos String, presta el de la derecha: s1 + &s2. Y format! siempre funciona y no mueve nada:
fn main() { let first = String::from("Hola, "); let second = String::from("Ferris");
let shout = format!("¡{first}{second}!"); // presta los dos let joined = first + &second; // first se mueve dentro de joined let short = "Buenas, ".to_owned() + "cangrejo"; // ¿&str a la izquierda? primero hazlo String
println!("{shout}"); println!("{joined}"); println!("{short}"); println!("{second} sigue aquí");}Salida:
¡Hola, Ferris!Hola, FerrisBuenas, cangrejoFerris sigue aquí+ toma el ownership (propiedad) del String de la izquierda y le añade el texto, por eso first ya no se puede usar después de esa línea. Si eso te lía, usa simplemente format!. Mira Strings.
Dónde lo verás normalmente
Sección titulada «Dónde lo verás normalmente»Comparar en código genérico. La clásica función largest falla con binary operation `>` cannot be applied to type `&T`, porque T podría ser cualquier tipo, incluso uno sin orden:
fn largest<T>(list: &[T]) -> &T { let mut largest = &list[0]; for item in list { if item > largest { // ❌ T podría no ser comparable largest = item; } } largest}Solución: añade una restricción de trait (trait bound), tal como sugiere rustc: fn largest<T: PartialOrd>(list: &[T]) -> &T. Mira Genéricos.
Comprobar el valor de un enum. if status == Status::Banned falla hasta que el enum tiene #[derive(PartialEq)]. Si no puedes cambiar el enum (viene de otro crate), usa matches!(status, Status::Banned) o un match.
Unir dos String. first + second con dos String no es E0369: es E0308 (“expected &str, found String”) con help: consider borrowing here, es decir, first + &second. Misma familia, misma solución que la 4.
Comparar dos tipos distintos. count == limit con un i32 y un f64 da E0308, y name == 'F' con un String y un char da E0277 (“can’t compare String with char”). Una regla práctica: te sale E0369 cuando el tipo de la izquierda no tiene ninguna versión del operador, y E0308 o E0277 cuando sí tiene una, pero no para ese tipo de la derecha. Solución: convierte uno de los lados (count as f64 == limit, name == "F").
Errores relacionados
Sección titulada «Errores relacionados»- E0277: trait bound not satisfied (trait no implementado): el operador existe para el tipo de la izquierda, pero no con ese tipo a la derecha (
i32 + f64,String == char). - E0308: mismatched types (tipos que no coinciden): lo que obtienes con
String + Stringo al comparar dos structs distintas. - E0599: no method found (método no encontrado):
.sort()o.contains()pueden fallar de la misma forma cuando a tu tipo le faltaOrdoPartialEq. - E0368: binary assignment operation
+=cannot be applied: la versión de este error para+=. ImplementaAddAssigndestd::ops. - Todos los errores del compilador: la lista completa de páginas de errores.
Comprueba lo que aprendiste
Sección titulada «Comprueba lo que aprendiste»Comprueba lo aprendido
1. Escribes if order_a == order_b para una struct Order y te sale E0369. ¿Cuál suele ser la solución?
2. Con #[derive(PartialOrd)] en struct Version { major: u32, minor: u32 }, ¿cómo se comparan dos versiones?
3. first y second son &str. ¿Qué expresión los une sin error?
Puntuación: 0 / 3
Para leer más
Sección titulada «Para leer más»- E0369 — índice de códigos de error de Rust (en inglés): la explicación oficial de este error.
- Traits derivables — El libro de Rust (apéndice C) (en inglés): qué te da derivar
PartialEqyPartialOrd, incluido el orden campo por campo. - Guardar texto UTF-8 con Strings — El libro de Rust (en inglés): por qué
+con cadenas pide unStringa la izquierda y un&stra la derecha, y cuándo usarformat!. - Operator Overloading — Rust by Example (en inglés): un ejemplo corto y ejecutable de cómo implementar
Addpara tus propios tipos. std::ops::Add— documentación de la biblioteca estándar (en inglés): el traitAdd, su tipoOutputy cómo sumar valores de tipos distintos.