Ir al contenido

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.

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`: falta PartialOrd (también para >, <=, >=).
  • cannot add `Point` to `Point` (no se puede sumar Point a Point): falta Add. También verás “cannot subtract”, “cannot multiply”, etc.
  • cannot add `&str` to `&str`: intentaste unir dos slices de cadena con +.

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.

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.

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 }: true
▶ Pruébalo tú mismo

Todos 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 favor
2.0 es más nueva que 1.7: true
▶ Pruébalo tú mismo

Si quieres usar .sort() en un Vec de tu tipo, también necesitarás Eq y Ord. Mira Ordenar y buscar.

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 }
true
▶ Pruébalo tú mismo

Sub, 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, Ferris
Buenas, cangrejo
Ferris sigue aquí
▶ Pruébalo tú mismo

+ 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.

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").

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