Pular para o conteúdo

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.

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

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.

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 }
▶ Experimente você mesmo

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 }
▶ Experimente você mesmo

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

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 em String antes.
  • s += t, com t também sendo uma String, não é E0368. É E0308, “expected &str, found String”, com a ajuda s += &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 #1
Ferris continua disponível
▶ Experimente você mesmo

Como 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]) -> T
where
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:

6
0.75
▶ Experimente você mesmo

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

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

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