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가 이렇게 하는 이유
섹션 제목: “Rust가 이렇게 하는 이유”Rust에서 변수는 기본적으로 불변이고, 참조자도 기본적으로 읽기 전용이에요. 무언가를 바꾸려면 명시적으로 선택해야 해요. 직접 소유한 변수라면 let mut, 빌린 값의 참조자라면 &mut예요. 변수와 대여를 참고하세요.
덕분에 코드를 믿기 쉬워져요. 함수가 &Vec<String>을 받는다면, 시그니처만 보고도 그 함수가 목록을 바꾸지 않는다는 걸 알 수 있어요. &mut Vec<String>을 받는다면 바꿀 수도 있다는 뜻이고요. 몰래 바뀌는 건 없어요.
이 규칙이 있어서 Rust의 더 큰 안전 보장도 가능해져요. “바꿀 수 있음”이 항상 코드에 적혀 있으니까, 컴파일러는 쓰는 쪽과 읽는 쪽이 동시에 존재하지 않도록 확인할 수 있어요(그게 E0502와 E0499예요). E0596은 그 첫 단계예요. 쓰기가 안전한지 검사하기 전에, 먼저 쓰겠다고 요청해야 하는 거죠.
고치는 방법
섹션 제목: “고치는 방법”방법 1: 변수에 mut 붙이기
섹션 제목: “방법 1: 변수에 mut 붙이기”값을 직접 소유하고 있고, 그 값이 바뀌어야 할 때 고르세요. 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);}실행 결과:
["테스트 작성", "버그 수정"]횟수: 2counter에 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 mutable4 | click(); // ❌ cannot borrow `click` as mutable | ----- cannot borrow as mutable5 | 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
더 알아보기
섹션 제목: “더 알아보기”- E0596 — Rust error codes index (영어): 이 에러에 대한 공식 설명이에요.
- 참조와 대여 — The Rust Programming Language 한국어판: “가변 참조자” 부분에서
&mut가 무엇이고 왜 직접 요청해야 하는지 설명해요. - 메서드 문법 — The Rust Programming Language 한국어판:
&self와&mut self의 차이, 그리고 각각이 호출하는 쪽에 무엇을 요구하는지 다뤄요. - 클로저 — The Rust Programming Language 한국어판: 클로저가 변수를 캡처하는 방법과, 값을 바꾸는 클로저가 왜
mut여야 하는지 설명해요. - Mutability (borrowing) — Rust by Example (영어):
&대여와&mut대여를 나란히 보여 주는 실행 예제예요.