콘텐츠로 이동

Rust 에러 E0596: cannot borrow as mutable, as it is not declared as mutable

E0596은 어떤 코드가 값을 바꾸려고 하는데(가변 대여, 즉 &mut가 필요해요), 여러분은 그 값을 읽을 권한만 줬다는 뜻이에요. 보통은 let에 mut를 깜빡했거나, 함수가 &mut가 필요한데 &를 받고 있는 경우예요. 아주 흔한 에러이고, 컴파일러의 help: 줄이 mut를 어디에 붙여야 하는지 거의 항상 정확히 알려 줘요. 이 페이지에서는 고치는 방법을 먼저 보여 드리고, 그다음에 설명할게요. 이 시리즈의 다른 에러는 Rust 컴파일러 에러 모음에서 볼 수 있어요.

error[E0596]: cannot borrow `tasks` as mutable, as it is not declared as mutable
--> src/main.rs:3:5
|
3 | tasks.push("write tests"); // ❌ push는 `tasks`를 바꿔야 해요
| ^^^^^ cannot borrow as mutable
|
help: consider changing this to be mutable
|
2 | let mut tasks = Vec::new();
| +++

“tasks가 가변으로 선언되지 않았으므로 가변으로 대여할 수 없다”는 뜻이에요. help:는 “이걸 가변으로 바꿔 보라”고 제안하고 있어요.

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

fn main() {
let tasks = Vec::new();
tasks.push("write tests"); // ❌ push는 `tasks`를 바꿔야 해요
println!("{tasks:?}");
}

두 번째 문구도 보게 될 거예요. “cannot borrow *tasks as mutable, as it is behind a & reference”(& 참조자 뒤에 있으므로 *tasks를 가변으로 대여할 수 없음). 에러 코드는 같고 원인이 살짝 달라요. 이 경우에는 변수가 & 참조자라서, 그 참조자가 가리키는 값을 바꿀 수 없는 거예요. 아래에서 두 경우를 모두 다뤄요.

3번째 줄의 tasks 아래에 있는 ^^^^^를 보세요. 항목을 추가하려면 Vec::push는 벡터의 가변 대여가 필요해요. 시그니처가 fn push(&mut self, value: T)거든요. 그런데 tasks는 그냥 let으로 만들었으니 읽기 전용이에요. 절대 바뀌지 않을 거라고 말한 값에 Rust가 &mut를 내줄 수는 없어요.

help: 줄이 해결 방법을 보여 줘요. let mut tasks예요. +++는 추가해야 할 세 글자의 위치예요.

“보기 전용” 권한으로 공유받은 문서를 떠올려 보세요. 모든 글자를 읽을 수는 있지만 “편집” 버튼은 회색으로 비활성화되어 있죠. mut(또는 &mut)가 바로 편집 권한을 요청하는 방법이에요.

Rust에서 변수는 기본적으로 불변이고, 참조자도 기본적으로 읽기 전용이에요. 무언가를 바꾸려면 명시적으로 선택해야 해요. 직접 소유한 변수라면 let mut, 빌린 값의 참조자라면 &mut예요. 변수와 대여를 참고하세요.

덕분에 코드를 믿기 쉬워져요. 함수가 &Vec<String>을 받는다면, 시그니처만 보고도 그 함수가 목록을 바꾸지 않는다는 걸 알 수 있어요. &mut Vec<String>을 받는다면 바꿀 수도 있다는 뜻이고요. 몰래 바뀌는 건 없어요.

이 규칙이 있어서 Rust의 더 큰 안전 보장도 가능해져요. “바꿀 수 있음”이 항상 코드에 적혀 있으니까, 컴파일러는 쓰는 쪽과 읽는 쪽이 동시에 존재하지 않도록 확인할 수 있어요(그게 E0502와 E0499예요). E0596은 그 첫 단계예요. 쓰기가 안전한지 검사하기 전에, 먼저 쓰겠다고 요청해야 하는 거죠.

값을 직접 소유하고 있고, 그 값이 바뀌어야 할 때 고르세요. push할 Vec, 뒤에 이어 붙일 String, &mut self 메서드를 호출할 구조체 같은 경우예요. 대부분은 이게 정답이에요.

struct Counter {
count: u32,
}
impl Counter {
fn increment(&mut self) {
self.count += 1;
}
}
fn main() {
let mut tasks = Vec::new();
tasks.push("테스트 작성");
tasks.push("버그 수정");
println!("{tasks:?}");
let mut counter = Counter { count: 0 };
counter.increment();
counter.increment();
println!("횟수: {}", counter.count);
}

실행 결과:

["테스트 작성", "버그 수정"]
횟수: 2
▶ 직접 실행해 보기

counter에 mut가 없으면 counter.increment()가 cannot borrow counter as mutable로 실패해요. &mut self를 받는 메서드를 호출하면 변수 전체를 가변으로 대여하기 때문에, 변수가 mut여야 해요. &self만 받는 메서드는 mut 없이도 잘 동작해요. 메서드로 동작 추가하기를 참고하세요.

함수가 소유하는 매개변수도 마찬가지예요. fn shout(s: String)은 s.push('!')를 호출할 수 없지만, fn shout(mut s: String)은 할 수 있어요.

방법 2: 매개변수를 &mut로 바꾸고 &mut x 넘기기

섹션 제목: “방법 2: 매개변수를 &mut로 바꾸고 &mut x 넘기기”

함수가 호출한 쪽이 소유한 값을 바꿔야 할 때 고르세요. 이 경우에는 “behind a & reference” 문구가 나와요.

fn add_task(tasks: &Vec<String>, task: &str) {
tasks.push(task.to_string()); // ❌ `tasks`는 `&` 참조자예요
}
error[E0596]: cannot borrow `*tasks` as mutable, as it is behind a `&` reference
--> src/main.rs:2:5
|
2 | tasks.push(task.to_string()); // ❌ `tasks`는 `&` 참조자예요
| ^^^^^ `tasks` is a `&` reference, so it cannot be borrowed as mutable
|
help: consider changing this to be a mutable reference
|
1 | fn add_task(tasks: &mut Vec<String>, task: &str) {
| +++

“tasks는 & 참조자라서 가변으로 대여할 수 없다”는 뜻이고, help:는 매개변수를 가변 참조자로 바꾸라고 제안해요.

세 군데를 고치세요. 매개변수 타입은 &mut Vec<String>으로, 호출하는 쪽 변수는 let mut로, 호출할 때는 &mut tasks를 넘기도록요.

fn add_task(tasks: &mut Vec<String>, task: &str) {
tasks.push(task.to_string());
}
fn main() {
let mut tasks = Vec::new();
add_task(&mut tasks, "테스트 작성");
add_task(&mut tasks, "문서 업데이트");
println!("할 일 {}개: {tasks:?}", tasks.len());
}

실행 결과:

할 일 2개: ["테스트 작성", "문서 업데이트"]
▶ 직접 실행해 보기

매개변수는 바꿨는데 여전히 &tasks를 넘기면, 대신 E0308이 types differ in mutability(가변성이 다른 타입)라는 메모와 함께 나와요. 호출하는 곳에도 &mut tasks라고 쓰라는 뜻일 뿐이에요. 호출하는 곳에 &mut를 쓰게 한 건 일부러 그런 거예요. main을 읽는 누구나 add_task가 tasks를 바꿀 수 있다는 걸 알 수 있으니까요.

방법 3: 클로저를 담은 변수에 mut 붙이기

섹션 제목: “방법 3: 클로저를 담은 변수에 mut 붙이기”

클로저가 캡처한 변수를 바꿀 때 고르세요. 그런 클로저는 실행될 때마다 자기 상태를 바꾸기 때문에, 호출하려면 클로저 자체를 가변으로 대여해야 해요.

fn main() {
let mut clicks = 0;
let click = || clicks += 1;
click(); // ❌ cannot borrow `click` as mutable
click();
println!("Clicks: {clicks}");
}
error[E0596]: cannot borrow `click` as mutable, as it is not declared as mutable
--> src/main.rs:3:9
|
3 | let click = || clicks += 1;
| ^^^^^ ------ calling `click` requires mutable binding due to mutable borrow of `clicks`
| |
| not mutable
4 | click(); // ❌ cannot borrow `click` as mutable
| ----- cannot borrow as mutable
5 | click();
| ----- cannot borrow as mutable
|
help: consider changing this to be mutable
|
3 | let mut click = || clicks += 1;
| +++

가운데 메모는 “clicks를 가변으로 대여하고 있으므로, click을 호출하려면 가변 바인딩이 필요하다”는 뜻이에요.

변수 clicks는 이미 mut예요. 빠진 건 클로저 쪽의 mut예요.

fn main() {
let mut clicks = 0;
let mut click = || clicks += 1;
click();
click();
click();
println!("클릭 수: {clicks}");
}

실행 결과:

클릭 수: 3
▶ 직접 실행해 보기

많은 사람이 여기서 놀라요. 클로저가 뭔가를 바꾸는 것처럼 “보이지” 않거든요. 하지만 클로저 안에 &mut clicks를 들고 있으니 FnMut 클로저이고, FnMut를 호출하려면 mut가 필요해요. 캡처한 변수를 바꾸는 클로저와 Fn, FnMut, FnOnce를 참고하세요.

방법 4: 반복문에서 iter_mut()나 &mut 쓰기

섹션 제목: “방법 4: 반복문에서 iter_mut()나 &mut 쓰기”

컬렉션을 돌면서 각 항목을 바꾸고 싶을 때 고르세요. .iter()와 for x in &list는 읽기 전용인 & 참조자를 내줘요.

for name in names.iter() {
name.push('!'); // ❌ cannot borrow `*name` as mutable, as it is behind a `&` reference
}

.iter_mut()나 &mut names로 바꾸세요(그리고 names 자체도 mut로 만드세요).

fn main() {
let mut names = vec![String::from("ferris"), String::from("corro")];
for name in names.iter_mut() {
name.push('!');
}
println!("{names:?}");
let mut prices = vec![10, 20, 30];
for price in &mut prices {
*price += 5;
}
println!("{prices:?}");
}

실행 결과:

["ferris!", "corro!"]
[15, 25, 35]
▶ 직접 실행해 보기

컴파일러도 이 방법을 제안해요. help: use mutable method ... names.iter_mut()처럼요. 반복자 체인 안의 클로저도 마찬가지예요. words.iter().for_each(|w| w.push('!'))는 실패하고, words.iter_mut().for_each(|w| w.push('!'))는 동작해요. 반복자를 참고하세요.

Vec나 String을 한 단계씩 만들 때. let list = Vec::new(); 다음에 list.push(...), 또는 let s = String::new(); 다음에 s.push_str(...). 해결: let mut. 처음부터 모든 항목을 알고 있다면 let list = vec![a, b, c];처럼 써서 mut가 아예 필요 없게 할 수도 있어요.

구조체의 “setter”나 “update” 메서드를 호출할 때. deposit이 &mut self를 받는다면 account.deposit(50)은 let mut account로 선언하지 않는 한 실패해요. 해결: let mut account = Account::new();. 구조체를 참고하세요.

습관처럼 &를 받는 도우미 함수. fn reset(board: &Board)라고 써 놓고 안에서 board를 바꾸려는 경우예요. 해결: board: &mut Board로 바꾸고 reset(&mut board)로 호출하세요. 좋은 습관이 있어요. 함수가 읽기만 하면 &, 바꾸면 &mut라고 쓰세요.

HashMap 안의 값을 바꿀 때. if let Some(v) = map.get("key") { v.push(..) }는 실패해요. get은 & 참조자를 돌려주기 때문이에요. 해결: map.get_mut("key")나 entry API를 쓰세요. 해시맵을 참고하세요.

  • E0384: cannot assign twice to immutable variable. 똑같이 mut가 빠진 경우지만, 메서드 호출이 아니라 x = ...나 x += 1에서 나요.
  • E0594: 필드에 대입하거나 & 참조자를 통해 대입할 수 없어요. 예를 들어 p가 mut가 아닌데 p.hp = 5;를 하거나, for x in &v 안에서 *x += 1을 할 때예요. 해결 방법도 같아요. let mut 또는 &mut.
  • E0308: mismatched types, types differ in mutability. &mut x를 원하는 함수에 &x를 넘겼을 때 나요.
  • E0502: &mut 권한을 얻었지만, 다른 곳에서 아직 그 값을 읽고 있어요.
  • E0499: 같은 값을 동시에 두 번 &mut로 대여했어요.
  • 모든 컴파일러 에러: 에러 페이지 전체 목록이에요.

이해도 점검

1. let scores = Vec::new(); scores.push(10);에서 E0596이 나요. 어떻게 고치나요?

2. 함수 fn add(list: &Vec<u32>) 안에서 list.push(1)을 호출해요. 무엇을 바꿔야 하나요?

3. let mut total = 0; let add = |x| total += x; add(5);가 실패해요. 왜일까요?

점수: 0 / 3