콘텐츠로 이동

Rust 에러 E0368: binary assignment operation += cannot be applied (복합 대입 불가)

E0368은 +=, -=, *= 같은 복합 대입 연산자를, 아직 그 연산을 지원하지 않는 타입에 썼다는 뜻이에요. E0369의 += 사촌이에요. 내가 만든 구조체는 +=를 저절로 얻지 못해요. 이미 +를 지원하더라도요. 보통은 짧은 impl AddAssign 블록으로 고쳐지고, 참조자 뒤에 있는 숫자라면 * 하나면 돼요. 이 시리즈의 다른 에러는 Rust 컴파일러 에러 해설에서 볼 수 있어요.

error[E0368]: binary assignment operation `+=` cannot be applied to type `Money`
--> src/main.rs:8:5
|
8 | wallet += tip; // ❌ Money는 +=를 할 줄 몰라요
| ------^^^^^^^
| |
| 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`

“복합 대입 연산 +=를 Money 타입에 적용할 수 없다”는 뜻이에요. note는 “Money에 AddAssign 구현이 빠진 것 같다”고 알려 줘요.

이 에러를 내는 프로그램은 다음과 같아요.

struct Money {
cents: i64,
}
fn main() {
let mut wallet = Money { cents: 500 };
let tip = Money { cents: 150 };
wallet += tip; // ❌ Money는 +=를 할 줄 몰라요
println!("{}", wallet.cents);
}

첫 줄의 연산자는 달라질 수 있지만, 같은 에러예요. p -= step은 SubAssign을 요구하고, p *= 2는 MulAssign<{integer}>를 요구해요. “정수로 곱해서 대입하기”라는 뜻이에요.

왼쪽 아래의 라벨이 무엇이 잘못됐는지 알려 줘요. “cannot use += on type Money”, 즉 “Money 타입에는 +=를 쓸 수 없다”예요. 그다음 note:가 내 구조체를 가리키면서 AddAssign을 구현해야 한다(must implement AddAssign)고 말해요. 이게 += 뒤에 있는 트레이트예요.

돼지 저금통을 떠올려 보세요. 동전을 넣는 건 분명한 동작이에요. 그런데 누가 “Money”라고 적힌 상자를 건네면서 “이 상자를 저 상자에 더해 줘”라고 하면, 어떻게 더할지 알아야 해요. 센트를 합칠까요? 상자를 쌓을까요? Rust는 그 규칙을 내가 한 번 적어 주기를 바라요.

모든 복합 대입 연산자는 사실 변장한 메서드 호출이에요. a += b는 AddAssign::add_assign(&mut a, b)를, a -= b는 SubAssign::sub_assign을 호출하는 식이에요. 이 트레이트들은 모두 std::ops에 있어요. 숫자는 이미 이것들을 구현하고 있어요. 새로 만든 구조체는 하나도 없는 상태로 시작하고, 수학 연산자에는 #[derive]도 없어요.

많은 사람이 놀라는 부분이 있어요. Add와 AddAssign은 별개의 트레이트예요. Point가 이미 Add를 구현했더라도 p += q는 여전히 E0368을 내요. 둘이 하는 일이 다르기 때문이에요. a + b는 값 두 개를 받아서 새 값을 돌려줘요. a += b는 &mut self를 통해 a를 그 자리에서 바꾸고 아무것도 돌려주지 않아요. 그 자리에서 바꾸는 쪽이 훨씬 쌀 때가 있어요. String의 +=는 새 문자열을 만들지 않고 기존 버퍼 뒤에 이어 붙여요. Rust는 타입마다 이걸 고를 수 있게 해 두었기 때문에, +=를 몰래 +로 바꿔 주지 않아요.

연산자 트레이트는 자주 쓰는 트레이트와 고급 트레이트에서 설명해요.

방법 1: AddAssign(과 친구들) 구현하기

섹션 제목: “방법 1: AddAssign(과 친구들) 구현하기”

돈을 더하거나 위치를 옮기는 것처럼, 내 타입에서 +=가 분명한 의미를 가질 때 이 방법을 고르세요. self를 바꾸는 메서드 하나만 쓰면 돼요. SubAssign, MulAssign 같은 다른 트레이트도 필요할 때 같은 방식으로 추가하세요.

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 }; // 팁을 받았어요
wallet -= Money { cents: 200 }; // 점심을 샀어요
println!("{:?}", wallet);
}

실행 결과:

Money { cents: 450 }
▶ 직접 실행해 보기

&mut self와 반환 타입이 없다는 점을 눈여겨보세요. Add와의 차이는 그게 전부예요. 안에서 self.cents += other.cents가 되는 건 i64에 이미 AddAssign이 있기 때문이에요.

방법 2: 이미 Add가 있다면 a = a + b를 쓰거나 재사용하기

섹션 제목: “방법 2: 이미 Add가 있다면 a = a + b를 쓰거나 재사용하기”

내 타입이 이미 Add를 구현하고 있을 때(예를 들어 E0369를 고친 뒤) 이 방법을 고르세요. 타입이 Copy라면 그냥 pos = pos + step이라고 쓰면 돼요. 이건 Add만 있으면 돼요. 아니면 Add를 호출하는 한 줄짜리 AddAssign을 구현해서 +=도 되게 할 수 있어요.

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; // Add를 재사용해요
}
}
fn main() {
let mut pos = Point { x: 0, y: 0 };
let step = Point { x: 1, y: 2 };
pos = pos + step; // Add만 있으면 돼요
pos += step; // AddAssign이 필요해요
println!("{:?}", pos);
}

실행 결과:

Point { x: 2, y: 4 }
▶ 직접 실행해 보기

*self = *self + other는 현재 값을 복사해 꺼내서 더한 다음, 결과를 다시 써 넣어요. 이게 되는 건 Point가 Copy이기 때문이에요. 힙 데이터를 소유하는 타입(예를 들어 Vec 필드가 있는 타입)이라면 방법 1처럼 그 자리에서 바꾸는 버전을 쓰세요.

방법 3: 문자열은 s += &t나 push_str

섹션 제목: “방법 3: 문자열은 s += &t나 push_str”

왼쪽이 문자열일 때 이 방법을 고르세요. +=는 소유한 String에 쓸 수 있고, 오른쪽은 &str이어야 해요. 많은 사람이 걸려 넘어지는 경우가 두 가지 있어요.

  • cannot be applied to type `&str`: 왼쪽이 "Hello, " 같은 문자열 슬라이스예요. &str은 읽기 전용으로 보는 것이라서 늘어날 수 없어요. 먼저 String으로 만드세요.
  • t도 String일 때 s += t는 E0368이 아니에요. s += &t라는 help와 함께 E0308(“expected &str, found String”)이 나요. 오른쪽을 빌리기만 하면 돼요.
fn main() {
let mut greeting = String::from("안녕하세요, "); // 소유한 값이라 늘어날 수 있어요
let name = String::from("Ferris");
greeting += &name; // 오른쪽을 빌려요
greeting.push_str("!"); // 같은 일을 메서드로 해요
let mut tag = "꽃게".to_string(); // 왼쪽이 &str이라면? String으로 만들어요
tag += " #1";
println!("{greeting}");
println!("{tag}");
println!("{name}는 아직 쓸 수 있어요");
}

실행 결과:

안녕하세요, Ferris!
꽃게 #1
Ferris는 아직 쓸 수 있어요
▶ 직접 실행해 보기

오른쪽은 빌리기만 했으니까 그 뒤에도 name은 여전히 내 거예요. +=와 push_str은 완전히 같은 일을 해요. 읽기 편한 쪽을 쓰세요. 문자열을 참고하세요.

방법 4: 제네릭 코드에는 T: AddAssign 바운드 추가하기

섹션 제목: “방법 4: 제네릭 코드에는 T: AddAssign 바운드 추가하기”

에러가 cannot be applied to type `T`라고 할 때 이 방법을 고르세요. 제네릭 함수 안에서 T는 어떤 타입이든 될 수 있고, +=가 없는 타입일 수도 있어요. rustc의 help가 무엇을 추가해야 하는지 정확히 알려 줘요. consider further restricting type parameter `T` with trait `AddAssign`, 즉 “타입 매개변수 T를 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]));
}

실행 결과:

6
0.75
▶ 직접 실행해 보기

이제 total은 복사할 수 있고, 시작 값이 있고, +=를 지원하는 모든 타입에서 동작해요. 정수, 부동소수점, 그리고 이 트레이트들을 구현한 뒤의 내 Money 타입까지요. 평범한 숫자라면 items.iter().sum() 한 줄로도 같은 일을 할 수 있어요. 제네릭을 참고하세요.

HashMap으로 개수를 셀 때. let count = counts.entry(word).or_insert(0); 다음에 count += 1;을 쓰면 cannot be applied to type `&mut i32`가 나요. count는 숫자가 아니라 참조자이기 때문이에요. rustc의 help는 “+= can be used on i32 if you dereference the left-hand side”(왼쪽을 역참조하면 i32에 +=를 쓸 수 있다)예요. 해결: *count += 1;. HashMap을 참고하세요.

구조체에 숫자를 곱할 때. p *= 2는 MulAssign<{integer}>가 필요해요. 오른쪽이 다른 Point가 아니라 숫자이기 때문이에요. 해결: 그 오른쪽 타입에 대해 트레이트를 구현하세요.

impl std::ops::MulAssign<i32> for Point {
fn mul_assign(&mut self, factor: i32) {
self.x *= factor;
self.y *= factor;
}
}

반복문에서 합계를 쌓을 때. price가 내가 만든 Money 타입일 때 total += item.price를 쓰는 경우예요. 방법 1로 해결돼요. prices.iter().copied().sum()으로 쓰고 싶다면 대신 Sum 트레이트가 필요하고, 없으면 E0277(“a value of type Money cannot be made by summing an iterator”, 반복자를 합해서 Money 값을 만들 수 없다)이 나요.

  • E0369: binary operation cannot be applied (이항 연산 적용 불가): +, ==, < 같은 대입하지 않는 연산자에서 생기는 같은 문제예요.
  • E0308: mismatched types (타입 불일치): String 두 개로 s += t를 쓸 때 나와요(빌리세요: s += &t).
  • E0277: trait bound not satisfied (트레이트 바운드 불충족): 왼쪽 타입에 +=는 있지만 이 오른쪽 타입과는 안 돼요. total이 정수인데 total += 2.5를 쓰면 cannot add-assign `{float}` to `{integer}`가 나는 것처럼요.
  • E0384: cannot assign twice (두 번 대입 불가): mut 없이 선언한 변수에 x += 1을 썼어요. 연산자는 문제가 없고, 변수가 가변이 아닐 뿐이에요.
  • 전체 컴파일러 에러: 에러 페이지 전체 목록이에요.

이해도 점검

1. Point가 Add를 구현했는데도 p += q는 여전히 E0368을 내요. 왜일까요?

2. count는 map.entry(word).or_insert(0)에서 왔어요. 컴파일되는 줄은 무엇일까요?

3. s와 t가 둘 다 String이에요. s 뒤에 t를 어떻게 이어 붙일까요?

점수: 0 / 3