콘텐츠로 이동

Rust 에러 E0384: cannot assign twice to immutable variable (불변 변수에 재할당)

E0384는 변수에 값을 한 번 넣은 뒤 다른 값을 또 넣으려고 했는데, 그 변수가 mut로 선언되지 않았다는 뜻이에요. Rust에서는 “이 변수는 바꿀 거예요”라고 직접 말하지 않으면 변수를 바꿀 수 없어요. Rust를 시작하고 첫 한 시간 안에 거의 모두가 이 에러를 만나요. 고치는 방법도 대개 세 글자, mut를 붙이는 거예요. 이 페이지에서는 고치는 방법을 먼저 보여 드리고, 그다음에 Rust가 왜 이렇게 동작하는지 설명할게요. 이 시리즈의 다른 에러는 Rust 컴파일러 에러 모음에서 볼 수 있어요.

error[E0384]: cannot assign twice to immutable variable `score`
--> src/main.rs:4:5
|
2 | let score = 10;
| ----- first assignment to `score`
3 | println!("Score: {score}");
4 | score = 15; // ❌ score는 mut가 아니에요
| ^^^^^^^^^^ cannot assign twice to immutable variable
|
help: consider making this binding mutable
|
2 | let mut score = 10;
| +++

“cannot assign twice to immutable variable”은 “불변(immutable) 변수에는 두 번 대입할 수 없다”는 뜻이에요. help:는 “이 바인딩을 가변으로 바꿔 보라”고 제안하고 있어요.

이 에러를 만드는 프로그램은 이거예요.

fn main() {
let score = 10;
println!("Score: {score}");
score = 15; // ❌ score는 mut가 아니에요
println!("New score: {score}");
}

변수가 함수 매개변수라면 문구가 살짝 바뀌어서 “cannot assign to immutable argument”(불변 인자에는 대입할 수 없음)가 나와요. 에러 코드도, 원리도 똑같아요.

컴파일러는 두 곳을 가리켜요.

  1. 2번째 줄(first assignment to score라고 표시된 곳): 여기서 score가 값을 받았어요. let mut가 아니라 let이라고 썼으니, 이 값은 이제 바꿀 수 없어요.
  2. 4번째 줄(^^^^로 표시된 곳): 여기서 새 값을 넣으려고 했고, Rust가 거절했어요.

help: 부분에 이미 답이 있어요. let mut score = 10;이에요. +++는 추가해야 할 세 글자의 위치를 알려 줘요.

let은 볼펜으로 쓰는 것, let mut는 연필로 쓰는 것이라고 생각해 보세요. 둘 다 괜찮아요. 하지만 볼펜으로 쓴 건 나중에 지울 수 없죠.

+=, -=, *= 같은 연산자에도 같은 규칙이 적용돼요. count += 1도 대입이니까 역시 mut가 필요해요.

Rust에서 모든 변수는 기본적으로 불변이에요. 값을 바꾸고 싶으면 mut로 직접 선택해야 해요. 자세한 내용은 변수 페이지에 있어요.

엄격하게 들리지만, 실제로는 큰 도움이 돼요. let total = ...이라고 쓰여 있으면 total은 스코프가 끝날 때까지 그 값 그대로라는 걸 알 수 있어요. 함수의 나머지 부분을 다 훑어보면서 어딘가에서 값이 바뀌었는지 확인할 필요가 없어요. 반대로 let mut가 보이면 “이건 바뀌니까 잘 지켜보세요”라는 분명한 신호예요.

진짜 버그도 잡아 줘요. 복사본을 바꾸려다 실수로 원본을 바꿨거나, 같은 이름을 실수로 다시 썼을 때 컴파일러가 막아 줘요. 그리고 한 번도 바뀌지 않는 mut 변수에는 경고(variable does not need to be mutable, “이 변수는 가변일 필요가 없어요”)를 띄워 줘요. 그래서 신호가 양쪽 방향으로 모두 정직하게 유지돼요.

변수가 정말로 시간이 지나면서 바뀌어야 할 때 고르세요. 카운터, 누적 합계, 점수, 상태 같은 것들이요. 대부분은 이게 정답이고, 컴파일러가 제안하는 방법도 바로 이거예요.

fn main() {
let mut score = 10;
println!("점수: {score}");
score = 15; // ✅ 이제 괜찮아요
println!("새 점수: {score}");
let mut count = 0;
for _ in 0..3 {
count += 1;
}
println!("횟수: {count}");
}

실행 결과:

점수: 10
새 점수: 15
횟수: 3
▶ 직접 실행해 보기

카운터가 대표적인 예예요. let count = 0;이라고 쓰고 반복문 안에서 count += 1;을 하면 += 줄에서 E0384가 나요. mut를 붙이면 해결돼요. mut로 바꿀 수 있는 변수 만들기도 참고하세요.

방법 2: 섀도잉으로 새 값 만들기

섹션 제목: “방법 2: 섀도잉으로 새 값 만들기”

변수를 정말로 바꾸고 싶은 게 아닐 때 고르세요. 이전 값으로 계산한 새 값이 필요하고(여러 단계에 걸쳐 만드는 경우가 많아요), 그다음부터는 바뀌지 않는 경우예요. 같은 이름으로 let을 한 번 더 쓰면 돼요.

fn main() {
let price = 20;
let price = price + 5; // 이전 price로 만든 새 `price`
let price = price * 2;
println!("최종 가격: {price}");
let input = " 42 ";
let input = input.trim(); // 아직 문자열
let input: u32 = input.parse().unwrap(); // 이제 숫자
println!("변환 결과: {}", input + 1);
}

실행 결과:

최종 가격: 50
변환 결과: 43
▶ 직접 실행해 보기

이걸 섀도잉(shadowing)이라고 해요. let을 쓸 때마다 완전히 새로운 변수가 만들어지고, 이전 변수는 가려져요. 좋은 점이 두 가지 있어요. 마지막 price는 불변이라서 나중에 실수로 바뀔 일이 없어요. 그리고 input이 &str에서 u32로 바뀐 것처럼 타입도 바꿀 수 있는데, 이건 mut로는 할 수 없어요. 자세한 내용은 섀도잉: 같은 이름 다시 쓰기에 있어요.

간단한 기준이 있어요. 값이 계속해서 바뀐다면(반복문 안에서, 시간이 지나면서) mut를 쓰세요. 값을 몇 단계에 걸쳐 준비하는 거라면 섀도잉이 읽기 좋아요.

방법 3: 함수 매개변수에 mut 붙이기

섹션 제목: “방법 3: 함수 매개변수에 mut 붙이기”

함수가 인자로 받은 자기 복사본을 바꾸고 싶을 때 고르세요. 매개변수도 불변이라서, 이 코드는 실패해요.

fn countdown(n: u32) {
while n > 0 {
println!("{n}...");
n -= 1; // ❌ cannot assign to immutable argument `n`
}
println!("Liftoff!");
}
error[E0384]: cannot assign to immutable argument `n`
--> src/main.rs:4:9
|
4 | n -= 1; // ❌ cannot assign to immutable argument `n`
| ^^^^^^ cannot assign to immutable argument
|
help: consider making this binding mutable
|
1 | fn countdown(mut n: u32) {
| +++

“불변 인자 n에는 대입할 수 없다”는 뜻이고, help:가 매개변수 이름 앞에 mut를 붙이라고 알려 줘요.

매개변수 이름 앞에 mut를 붙이세요.

fn countdown(mut n: u32) {
while n > 0 {
println!("{n}...");
n -= 1;
}
println!("발사!");
}
fn main() {
let start = 3;
countdown(start);
println!("start는 여전히 {start}");
}

실행 결과:

3...
2...
1...
발사!
start는 여전히 3
▶ 직접 실행해 보기

main의 start는 바뀌지 않았다는 점을 눈여겨보세요. mut n은 함수가 가진 자기 복사본만 바꿀 수 있게 해 줘요. 호출하는 쪽은 mut가 필요 없고, 영향도 받지 않아요. 함수가 호출한 쪽의 변수를 바꾸게 하고 싶다면 &mut u32를 받고 &mut start를 넘기세요. 함수와 대여를 참고하세요.

for 반복문 변수를 바꿀 때. 반복문 변수는 매 회차마다 새로 만들어지는 불변 변수예요.

for i in 0..5 {
if i == 2 {
i += 1; // ❌ cannot assign twice to immutable variable `i`
}
println!("{i}");
}

해결: 반복문의 카운터 자체를 바꾸고 싶은 경우는 드물어요. continue로 건너뛰거나, .step_by(2)를 쓰거나, 완전히 직접 제어하고 싶다면 let mut i와 while 반복문으로 바꾸세요. (for mut i in 0..5는 컴파일되지만, 이번 회차의 복사본만 바꿀 뿐 아무것도 건너뛰지 않아요.) break와 continue, while 반복문을 참고하세요.

먼저 선언하고 나중에 대입할 때. 값 없이 let total;이라고 쓰는 건 괜찮고, 대입도 한 번은 할 수 있어요. 두 번째 대입에서 E0384가 나요.

let total;
total = 5;
total = 6; // ❌ cannot assign twice to immutable variable `total`

해결: 정말로 바뀌는 값이라면 let mut total을 쓰세요. 둘 중 하나의 값을 고르려던 거라면 식 하나로 계산하세요. let total = if big_order { 6 } else { 5 };처럼요. if / else를 참고하세요.

합계를 누적할 때. let sum = 0;을 쓰고 반복문 안에서 sum += price;를 하는 게 가장 흔한 E0384예요. 해결: let mut sum = 0;을 쓰거나, 변수 없이 let sum: u32 = prices.iter().sum();이라고 쓰세요. 반복자를 참고하세요.

  • E0596: cannot borrow as mutable, as it is not declared as mutable. 똑같이 “mut를 깜빡한” 문제지만, 대입이 아니라 .push()처럼 값을 바꾸는 메서드를 호출할 때 나요.
  • E0594: cannot assign to p.hp, as p is not declared as mutable. 구조체 필드에 대입하거나(p.hp = 5;) & 참조자를 통해 값을 쓰려고 할 때 나요. 해결: let mut p, 또는 &mut 참조자. 구조체를 가변으로 만들기를 참고하세요.
  • E0502: 값이 읽기용으로 대여된 동안 그 값을 바꾸려고 했어요.
  • E0382: 이동된 값을 다시 사용했어요.
  • 모든 컴파일러 에러: 에러 페이지 전체 목록이에요.

이해도 점검

1. let lives = 3;이라고 쓰고 나중에 lives -= 1;을 했어요. E0384가 나는 이유는 무엇인가요?

2. name을 한 번만 정리하고(공백을 없애고 대문자로 바꾸고) 그 뒤로는 바꾸지 않으려고 해요. 가장 읽기 좋은 방법은 무엇인가요?

3. fn halve(n: u32) 안에서 n /= 2;를 했더니 E0384가 났어요. 호출하는 쪽은 그대로 두는 해결 방법은 무엇인가요?

점수: 0 / 3