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”(불변 인자에는 대입할 수 없음)가 나와요. 에러 코드도, 원리도 똑같아요.
무슨 뜻인가요
섹션 제목: “무슨 뜻인가요”컴파일러는 두 곳을 가리켜요.
- 2번째 줄(
first assignment to score라고 표시된 곳): 여기서score가 값을 받았어요.let mut가 아니라let이라고 썼으니, 이 값은 이제 바꿀 수 없어요. - 4번째 줄(
^^^^로 표시된 곳): 여기서 새 값을 넣으려고 했고, Rust가 거절했어요.
help: 부분에 이미 답이 있어요. let mut score = 10;이에요. +++는 추가해야 할 세 글자의 위치를 알려 줘요.
let은 볼펜으로 쓰는 것, let mut는 연필로 쓰는 것이라고 생각해 보세요. 둘 다 괜찮아요. 하지만 볼펜으로 쓴 건 나중에 지울 수 없죠.
+=, -=, *= 같은 연산자에도 같은 규칙이 적용돼요. count += 1도 대입이니까 역시 mut가 필요해요.
Rust가 이렇게 하는 이유
섹션 제목: “Rust가 이렇게 하는 이유”Rust에서 모든 변수는 기본적으로 불변이에요. 값을 바꾸고 싶으면 mut로 직접 선택해야 해요. 자세한 내용은 변수 페이지에 있어요.
엄격하게 들리지만, 실제로는 큰 도움이 돼요. let total = ...이라고 쓰여 있으면 total은 스코프가 끝날 때까지 그 값 그대로라는 걸 알 수 있어요. 함수의 나머지 부분을 다 훑어보면서 어딘가에서 값이 바뀌었는지 확인할 필요가 없어요. 반대로 let mut가 보이면 “이건 바뀌니까 잘 지켜보세요”라는 분명한 신호예요.
진짜 버그도 잡아 줘요. 복사본을 바꾸려다 실수로 원본을 바꿨거나, 같은 이름을 실수로 다시 썼을 때 컴파일러가 막아 줘요. 그리고 한 번도 바뀌지 않는 mut 변수에는 경고(variable does not need to be mutable, “이 변수는 가변일 필요가 없어요”)를 띄워 줘요. 그래서 신호가 양쪽 방향으로 모두 정직하게 유지돼요.
고치는 방법
섹션 제목: “고치는 방법”방법 1: mut 붙이기
섹션 제목: “방법 1: mut 붙이기”변수가 정말로 시간이 지나면서 바뀌어야 할 때 고르세요. 카운터, 누적 합계, 점수, 상태 같은 것들이요. 대부분은 이게 정답이고, 컴파일러가 제안하는 방법도 바로 이거예요.
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는 여전히 3main의 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, aspis 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
더 알아보기
섹션 제목: “더 알아보기”- E0384 — Rust error codes index (영어): 이 에러에 대한 공식 설명과 예제예요.
- 변수와 가변성 — The Rust Programming Language 한국어판:
let,mut, 상수, 섀도잉을 다루는 장이에요. - Mutability — Rust by Example (영어):
let과let mut를 비교하는 짧은 실행 예제예요. - Freezing — Rust by Example (영어): 가변 변수를 불변 변수로 섀도잉하면 값이 “얼어붙는” 모습을 보여 줘요.