Erro E0368 do Rust: binary assignment operation += cannot be applied to type
O E0368 quer dizer que você usou um operador composto como +=, -= ou *= num tipo que ainda não aceita esse operador. É o primo do E0369 para o +=. As suas structs não ganham += automaticamente, nem quando já aceitam +. A correção costuma ser um bloco impl AddAssign curto e, com números atrás de uma referência, um único *. Para os outros erros desta série, veja Erros do compilador Rust.
A mensagem de erro
Seção intitulada “A mensagem de erro”error[E0368]: binary assignment operation `+=` cannot be applied to type `Money` --> src/main.rs:8:5 |8 | wallet += tip; // ❌ Money não sabe fazer += | ------^^^^^^^ | | | 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`Em português: “a operação de atribuição binária += não pode ser aplicada ao tipo Money”. A nota diz que talvez falte uma implementação de AddAssign para Money.
Este é o programa que gera o erro:
struct Money { cents: i64,}
fn main() { let mut wallet = Money { cents: 500 }; let tip = Money { cents: 150 }; wallet += tip; // ❌ Money não sabe fazer += println!("{}", wallet.cents);}O operador na primeira linha muda, mas o erro é o mesmo. p -= step pede SubAssign, e p *= 2 pede MulAssign<{integer}>, que quer dizer “multiplicar e atribuir por um número inteiro”.
O que significa
Seção intitulada “O que significa”O rótulo embaixo do lado esquerdo diz o que deu errado: “cannot use += on type Money” (não dá para usar += no tipo Money). Depois, a note: aponta para a sua struct e diz que ela precisa implementar AddAssign (must implement AddAssign). Essa é a trait por trás do +=.
Pense num cofrinho. Colocar uma moeda nele é uma ação clara. Mas, se alguém entrega uma caixa com a etiqueta “Dinheiro” e diz “some esta outra caixa a ela”, você precisa saber como: somar os centavos? empilhar as caixas? O Rust precisa que você escreva essa regra uma vez.
Por que o Rust faz isso
Seção intitulada “Por que o Rust faz isso”Todo operador composto é uma chamada de método disfarçada. a += b chama AddAssign::add_assign(&mut a, b), a -= b chama SubAssign::sub_assign, e assim por diante. Todas essas traits ficam em std::ops. Os números já as implementam. A sua struct nova começa sem nenhuma, e não existe #[derive] para operadores matemáticos.
Esta é a parte que surpreende: Add e AddAssign são traits separadas. Mesmo que Point já implemente Add, p += q continua dando E0368. O motivo é que elas fazem trabalhos diferentes. a + b recebe dois valores e retorna um novo. a += b altera a no lugar por meio de &mut self e não retorna nada. Alterar no lugar pode ser bem mais barato: o += da String acrescenta texto ao buffer que já existe, em vez de montar uma string nova. O Rust deixa cada tipo escolher, então nunca transforma += em + por conta própria.
As traits de operadores são explicadas em Traits comuns e Traits avançados.
Como corrigir
Seção intitulada “Como corrigir”Correção 1: Implemente AddAssign (e as parecidas)
Seção intitulada “Correção 1: Implemente AddAssign (e as parecidas)”Escolha esta quando += no seu tipo tem um significado claro, como somar dinheiro ou mudar uma posição. Você escreve um método que altera self. Acrescente SubAssign, MulAssign e as outras do mesmo jeito quando precisar:
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 }; // ganhou uma gorjeta wallet -= Money { cents: 200 }; // pagou o almoço println!("{:?}", wallet);}Saída:
Money { cents: 450 }Repare no &mut self e na falta de tipo de retorno. Essa é toda a diferença em relação ao Add. Lá dentro, self.cents += other.cents funciona porque i64 já tem AddAssign.
Correção 2: Já tem Add? Escreva a = a + b ou reaproveite
Seção intitulada “Correção 2: Já tem Add? Escreva a = a + b ou reaproveite”Escolha esta quando o seu tipo já implementa Add (talvez depois de corrigir um E0369). Se o tipo for Copy, você pode simplesmente escrever pos = pos + step, que só precisa de Add. Ou implemente AddAssign em uma linha chamando o seu Add, para que o += funcione também:
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; // reaproveita o Add }}
fn main() { let mut pos = Point { x: 0, y: 0 }; let step = Point { x: 1, y: 2 }; pos = pos + step; // só precisa de Add pos += step; // precisa de AddAssign println!("{:?}", pos);}Saída:
Point { x: 2, y: 4 }*self = *self + other copia o valor atual, faz a soma e grava o resultado de volta. Isso só funciona porque Point é Copy. Para um tipo que guarda dados no heap (como um campo Vec), escreva a versão que altera no lugar, da Correção 1.
Correção 3: Strings: s += &t ou push_str
Seção intitulada “Correção 3: Strings: s += &t ou push_str”Escolha esta quando o lado esquerdo é uma string. O += funciona numa String com dono, e o lado direito precisa ser um &str. Dois casos costumam confundir:
cannot be applied to type `&str`: o lado esquerdo é um slice de string, como"Hello, ". Um&stré uma visão só de leitura, então não pode crescer. Transforme emStringantes.s += t, comttambém sendo umaString, não é E0368. É E0308, “expected&str, foundString”, com a ajudas += &t. É só emprestar o lado direito.
fn main() { let mut greeting = String::from("Olá, "); // tem dono, então pode crescer let name = String::from("Ferris");
greeting += &name; // empresta o lado direito greeting.push_str("!"); // a mesma coisa, como método
let mut tag = "caranguejo".to_string(); // &str à esquerda? transforme em String tag += " #1";
println!("{greeting}"); println!("{tag}"); println!("{name} continua disponível");}Saída:
Olá, Ferris!caranguejo #1Ferris continua disponívelComo o lado direito só é emprestado, name continua sendo seu depois. += e push_str fazem exatamente a mesma coisa; use o que ficar mais fácil de ler. Veja Strings.
Correção 4: Acrescente uma restrição T: AddAssign em código genérico
Seção intitulada “Correção 4: Acrescente uma restrição T: AddAssign em código genérico”Escolha esta quando o erro diz cannot be applied to type `T`. Dentro de uma função genérica, T pode ser qualquer tipo, até um sem +=. A ajuda do rustc diz exatamente o que acrescentar: consider further restricting type parameter `T` with trait `AddAssign` (considere restringir mais o parâmetro de tipo T com a 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]));}Saída:
60.75Agora total funciona com qualquer tipo que pode ser copiado, tem um valor inicial e aceita +=: inteiros, floats e o seu próprio Money, assim que ele implementar essas traits. Para números simples, items.iter().sum() faz o mesmo trabalho numa linha. Veja Genéricos.
Onde você costuma ver esse erro
Seção intitulada “Onde você costuma ver esse erro”Contando com um HashMap. let count = counts.entry(word).or_insert(0); seguido de count += 1; dá cannot be applied to type `&mut i32`. count é uma referência, não o número. A ajuda do rustc: “+= can be used on i32 if you dereference the left-hand side” (dá para usar += em i32 se você desreferenciar o lado esquerdo). Correção: *count += 1;. Veja HashMap.
Multiplicando uma struct por um número. p *= 2 pede MulAssign<{integer}>, porque o lado direito é um número, não outro Point. Correção: implemente a trait para esse tipo do lado direito:
impl std::ops::MulAssign<i32> for Point { fn mul_assign(&mut self, factor: i32) { self.x *= factor; self.y *= factor; }}Totais acumulados num loop. total += item.price, com price sendo o seu próprio tipo Money. A Correção 1 resolve. Se preferir escrever prices.iter().copied().sum(), isso precisa da trait Sum, e sem ela você recebe o E0277 (“a value of type Money cannot be made by summing an iterator”, um valor do tipo Money não pode ser obtido somando um iterador).
Erros relacionados
Seção intitulada “Erros relacionados”- E0369: binary operation cannot be applied: o mesmo problema para
+,==,<e os outros operadores que não atribuem. - E0308: mismatched types: o que você recebe com
s += tsendo as duasString(empreste:s += &t). - E0277: trait bound not satisfied: o
+=existe para o tipo da esquerda, mas não com esse tipo da direita, comocannot add-assign `{float}` to `{integer}`emtotal += 2.5quandototalé inteiro. - E0384: cannot assign twice:
x += 1numa variável declarada semmut. O operador está certo; a variável é que não é mutável. - Todos os erros do compilador: a lista completa de páginas de erro.
Teste o que aprendeu
Seção intitulada “Teste o que aprendeu”Teste o que aprendeu
1. Point implementa Add, mas p += q continua dando E0368. Por quê?
2. count vem de map.entry(word).or_insert(0). Qual linha compila?
3. s e t são String. Como você acrescenta t ao final de s?
Pontuação: 0 / 3
Para saber mais
Seção intitulada “Para saber mais”- E0368 — Rust error codes index (em inglês): a explicação oficial deste erro.
std::ops::AddAssign— documentação da biblioteca padrão (em inglês): a trait por trás do+=, com um exemplo;SubAssign,MulAssigne as outras estão listadas emstd::ops.- Advanced Traits — The Rust Book (em inglês): sobrecarga de operadores com
std::opse parâmetros de tipo padrão comoRhs. - Storing UTF-8 Encoded Text with Strings — The Rust Book (em inglês): por que strings crescem com
push_stre por que o+pede um&strà direita. - Operator Overloading — Rust by Example (em inglês): um exemplo curto para rodar, implementando traits de operadores nos seus próprios tipos.