Error E0368 de Rust: binary assignment operation += cannot be applied to type
E0368 significa que usaste un operador compuesto como +=, -= o *= con un tipo que todavía no lo admite. Es el primo de E0369 para +=. Tus propias structs no reciben += automáticamente, ni siquiera cuando ya admiten +. Normalmente la solución es un bloque impl AddAssign corto, y con números detrás de una referencia basta con un *. 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[E0368]: binary assignment operation `+=` cannot be applied to type `Money` --> src/main.rs:8:5 |8 | wallet += tip; // ❌ Money no sabe hacer += | ------^^^^^^^ | | | cannot use `+=` on type `Money` |note: an implementation of `AddAssign` might be missing for `Money` --> src/main.rs:1:1 |1 | struct Money { | ^^^^^^^^^^^^ must implement `AddAssign`El compilador dice que la operación de asignación += no se puede aplicar al tipo Money, y que quizá falte una implementación de AddAssign.
Este es el programa que lo provoca:
struct Money { cents: i64,}
fn main() { let mut wallet = Money { cents: 500 }; let tip = Money { cents: 150 }; wallet += tip; // ❌ Money no sabe hacer += println!("{}", wallet.cents);}El operador de la primera línea cambia, pero es el mismo error. p -= step pide SubAssign, y p *= 2 pide MulAssign<{integer}>, que significa “multiplicar y asignar por un número entero”.
Qué significa
Sección titulada «Qué significa»La etiqueta bajo el lado izquierdo dice qué salió mal: “cannot use += on type Money” (no se puede usar += con el tipo Money). Después, la note: señala tu struct y dice que debe implementar AddAssign. Ese es el trait que hay detrás de +=.
Piensa en una hucha. Echar una moneda es una acción clara. Pero si alguien te da una caja con la etiqueta “Dinero” y te dice “súmale esta otra caja”, necesitas saber cómo: ¿sumas los céntimos?, ¿apilas las cajas? Rust necesita que escribas esa regla una vez.
Por qué Rust hace esto
Sección titulada «Por qué Rust hace esto»Cada operador compuesto es una llamada a un método disfrazada. a += b llama a AddAssign::add_assign(&mut a, b), a -= b llama a SubAssign::sub_assign, y así con los demás. Todos estos traits (rasgos) están en std::ops. Los números ya los implementan. Tu struct nueva empieza sin ninguno, y no existe #[derive] para los operadores matemáticos.
Esta es la parte que sorprende a la gente: Add y AddAssign son traits separados. Aunque Point ya implemente Add, p += q sigue dando E0368. El motivo es que hacen trabajos distintos. a + b toma dos valores y devuelve uno nuevo. a += b cambia a en su sitio a través de &mut self y no devuelve nada. Cambiar en su sitio puede ser mucho más barato: el += de String añade el texto al búfer que ya existe en lugar de crear una cadena nueva. Rust deja que cada tipo elija, así que nunca convierte += en + por su cuenta.
Los traits de operadores se explican en Traits comunes y Traits avanzados.
Cómo solucionarlo
Sección titulada «Cómo solucionarlo»Solución 1: Implementa AddAssign (y sus hermanos)
Sección titulada «Solución 1: Implementa AddAssign (y sus hermanos)»Elige esta cuando += tiene un significado claro para tu tipo, como sumar dinero o mover una posición. Escribes un método que cambia self. Añade SubAssign, MulAssign y los demás de la misma forma cuando los necesites:
use std::ops::{AddAssign, SubAssign};
#[derive(Debug)]struct Money { cents: i64,}
impl AddAssign for Money { fn add_assign(&mut self, other: Money) { self.cents += other.cents; }}
impl SubAssign for Money { fn sub_assign(&mut self, other: Money) { self.cents -= other.cents; }}
fn main() { let mut wallet = Money { cents: 500 }; wallet += Money { cents: 150 }; // una propina wallet -= Money { cents: 200 }; // el almuerzo println!("{:?}", wallet);}Salida:
Money { cents: 450 }Fíjate en &mut self y en que no hay tipo de retorno. Esa es toda la diferencia con Add. Dentro, self.cents += other.cents funciona porque i64 ya tiene AddAssign.
Solución 2: ¿Ya tienes Add? Escribe a = a + b, o reutilízalo
Sección titulada «Solución 2: ¿Ya tienes Add? Escribe a = a + b, o reutilízalo»Elige esta cuando tu tipo ya implementa Add (quizá después de arreglar un E0369). Si el tipo es Copy, puedes escribir simplemente pos = pos + step, que solo necesita Add. O implementa AddAssign en una línea llamando a tu Add, para que += también funcione:
use std::ops::{Add, AddAssign};
#[derive(Debug, Clone, Copy)]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 } }}
impl AddAssign for Point { fn add_assign(&mut self, other: Point) { *self = *self + other; // reutiliza Add }}
fn main() { let mut pos = Point { x: 0, y: 0 }; let step = Point { x: 1, y: 2 }; pos = pos + step; // solo necesita Add pos += step; // necesita AddAssign println!("{:?}", pos);}Salida:
Point { x: 2, y: 4 }*self = *self + other copia el valor actual, suma y vuelve a escribir el resultado. Eso solo funciona porque Point es Copy. Para un tipo que tiene datos en el heap (como un campo Vec), escribe la versión que cambia en su sitio de la solución 1.
Solución 3: Cadenas: s += &t o push_str
Sección titulada «Solución 3: Cadenas: s += &t o push_str»Elige esta cuando el lado izquierdo es una cadena de texto. += funciona con un String con dueño, y el lado derecho tiene que ser un &str. Hay dos casos que confunden a la gente:
cannot be applied to type `&str`: el lado izquierdo es un slice de cadena como"Hola, ". Un&stres una vista de solo lectura, así que no puede crecer. Conviértelo antes en unString.s += t, cuandottambién es unString, no es E0368. Es E0308, “expected&str, foundString”, con la ayudas += &t. Solo tienes que prestar el lado derecho.
fn main() { let mut greeting = String::from("¡Hola, "); // tiene dueño, así que puede crecer let name = String::from("Ferris");
greeting += &name; // presta el lado derecho greeting.push_str("!"); // lo mismo, como método
let mut tag = "cangrejo".to_string(); // ¿&str a la izquierda? hazlo String tag += " #1";
println!("{greeting}"); println!("{tag}"); println!("{name} todavía se puede usar");}Salida:
¡Hola, Ferris!cangrejo #1Ferris todavía se puede usarComo el lado derecho solo se presta, name sigue siendo tuyo después. += y push_str hacen exactamente lo mismo; usa el que se lea mejor. Mira Strings.
Solución 4: Añade una restricción T: AddAssign en código genérico
Sección titulada «Solución 4: Añade una restricción T: AddAssign en código genérico»Elige esta cuando el error dice cannot be applied to type `T`. Dentro de una función genérica, T podría ser cualquier tipo, incluso uno sin +=. La ayuda de rustc dice exactamente qué añadir: consider further restricting type parameter `T` with trait `AddAssign` (restringe más el parámetro de tipo T con el trait AddAssign).
use std::ops::AddAssign;
fn total<T>(items: &[T]) -> Twhere T: Copy + Default + AddAssign,{ let mut sum = T::default(); for &item in items { sum += item; } sum}
fn main() { println!("{}", total(&[1, 2, 3])); println!("{}", total(&[0.5, 0.25]));}Salida:
60.75Ahora total funciona con cualquier tipo que se pueda copiar, tenga un valor inicial y admita +=: enteros, números con decimales y tu propio Money en cuanto implemente esos traits. Para números normales, items.iter().sum() hace el mismo trabajo en una línea. Mira Genéricos.
Dónde lo verás normalmente
Sección titulada «Dónde lo verás normalmente»Contar con un HashMap. let count = counts.entry(word).or_insert(0); y luego count += 1; da cannot be applied to type `&mut i32`. count es una referencia, no el número. La ayuda de rustc: “+= can be used on i32 if you dereference the left-hand side” (puedes usar += con i32 si desreferencias el lado izquierdo). Solución: *count += 1;. Mira HashMap.
Escalar una struct por un número. p *= 2 necesita MulAssign<{integer}>, porque el lado derecho es un número, no otro Point. Solución: implementa el trait para ese tipo del lado derecho:
impl std::ops::MulAssign<i32> for Point { fn mul_assign(&mut self, factor: i32) { self.x *= factor; self.y *= factor; }}Totales acumulados en un bucle. total += item.price, donde price es tu propio tipo Money. La solución 1 hace que funcione. Si prefieres escribir prices.iter().copied().sum(), eso necesita el trait Sum, y sin él obtienes E0277 (“a value of type Money cannot be made by summing an iterator”, no se puede obtener un Money sumando un iterador).
Errores relacionados
Sección titulada «Errores relacionados»- E0369: binary operation cannot be applied (operación binaria no admitida): el mismo problema con
+,==,<y los demás operadores que no asignan. - E0308: mismatched types (tipos que no coinciden): lo que obtienes con
s += tcuando los dos sonString(préstalo:s += &t). - E0277: trait bound not satisfied (trait no implementado):
+=existe para el tipo de la izquierda, pero no con ese tipo a la derecha, comocannot add-assign `{float}` to `{integer}`entotal += 2.5cuandototales un entero. - E0384: cannot assign twice (asignar dos veces a inmutable):
x += 1en una variable declarada sinmut. El operador está bien; lo que pasa es que la variable no es mutable. - 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. Point implementa Add, pero p += q sigue dando E0368. ¿Por qué?
2. count sale de map.entry(word).or_insert(0). ¿Qué línea compila?
3. s y t son String. ¿Cómo añades t al final de s?
Puntuación: 0 / 3
Para leer más
Sección titulada «Para leer más»- E0368 — índice de códigos de error de Rust (en inglés): la explicación oficial de este error.
std::ops::AddAssign— documentación de la biblioteca estándar (en inglés): el trait que hay detrás de+=, con un ejemplo;SubAssign,MulAssigny los demás aparecen enstd::ops.- Traits avanzados — El libro de Rust (en inglés): sobrecarga de operadores con
std::opsy parámetros de tipo por defecto comoRhs. - Guardar texto UTF-8 con Strings — El libro de Rust (en inglés): por qué las cadenas crecen con
push_stry por qué+pide un&stra la derecha. - Operator Overloading — Rust by Example (en inglés): un ejemplo corto y ejecutable de cómo implementar traits de operadores para tus propios tipos.