Rust 에러 E0502: cannot borrow as mutable because it is also borrowed as immutable
E0502는 코드의 다른 부분이 참조자로 값을 아직 읽고 있는데, 그 값을 바꾸려고 했다는 뜻이에요. 벡터를 다룰 때 정말 자주 나오고, 보통은 한 줄을 옮기거나 값 하나를 복사하면 해결돼요. 이 페이지에서는 고치는 방법을 먼저 보여 주고, 그다음에 Rust가 왜 이걸 고집하는지 설명할게요. 이 시리즈의 다른 에러는 Rust 컴파일러 에러 해설에서 볼 수 있어요.
에러 메시지
섹션 제목: “에러 메시지”error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable --> src/main.rs:4:5 |3 | let first = &v[0]; // Vec 안을 가리키는 참조자 | - immutable borrow occurs here4 | v.push(4); // ❌ `first`가 살아 있는 동안 Vec을 바꿔요 | ^^^^^^^^^ mutable borrow occurs here5 | println!("First: {first}"); // ...여기서 `first`를 쓰기 때문이에요 | ----- immutable borrow later used here“v가 이미 불변으로 대여되어 있어서 가변으로 대여할 수 없다”는 뜻이에요. 라벨은 차례대로 “불변 대여가 여기서 일어남”, “가변 대여가 여기서 일어남”, “불변 대여가 나중에 여기서 쓰임”이라고 말해요.
이 에러를 내는 프로그램은 다음과 같아요.
fn main() { let mut v = vec![1, 2, 3]; let first = &v[0]; // Vec 안을 가리키는 참조자 v.push(4); // ❌ `first`가 살아 있는 동안 Vec을 바꿔요 println!("First: {first}"); // ...여기서 `first`를 쓰기 때문이에요}무슨 뜻인가요
섹션 제목: “무슨 뜻인가요”컴파일러가 세 줄을 가리키는데, 에러를 이해하려면 세 줄이 모두 필요해요.
- 3번째 줄:
&v[0]이v안을 가리키는 읽기 전용 참조자를 만들어요. 이게 불변 대여(immutable borrow)예요. - 4번째 줄:
v.push(4)는v를 바꿔야 하니까 가변 대여(mutable borrow)를 해요. - 5번째 줄:
first를 다시 써요. 이 줄 때문에 첫 번째 대여가 4번째 줄을 지나서까지 살아 있게 돼요.
5번째 줄이 없다면 코드는 컴파일돼요. 문제는 두 대여가 존재한다는 게 아니라, 두 대여가 겹친다는 거예요.
Rust가 이렇게 하는 이유
섹션 제목: “Rust가 이렇게 하는 이유”규칙은 이거예요. 읽는 쪽은 여럿이어도 되고, 쓰는 쪽은 하나만. 둘이 동시에 있으면 안 돼요. 대여 페이지에서 배운 규칙이에요.
Vec에는 아주 구체적인 이유가 있어요. Vec은 요소들을 메모리의 한 블록에 담아 둬요. push를 했는데 블록이 꽉 차 있으면, Vec은 모든 요소를 더 큰 새 블록으로 옮기고 예전 블록을 해제해요. 그러면 예전 블록을 가리키던 참조자(first 같은)는 해제된 메모리를 가리키게 돼요. C++에서는 이게 그 유명한 “반복자 무효화”(iterator invalidation) 버그이고, 프로그램이 죽거나 조용히 쓰레기 값을 읽게 돼요.
친구 주소를 적어 뒀는데 친구가 더 큰 집으로 이사 간 것과 비슷해요. 메모는 멀쩡해 보여도, 가리키는 곳은 빈 건물이에요. Rust는 빈 건물에 찾아가게 두는 대신, 아예 컴파일을 거부해요.
고치는 방법
섹션 제목: “고치는 방법”방법 1: 다 읽고 나서 쓰기
섹션 제목: “방법 1: 다 읽고 나서 쓰기”가장 먼저 시도해 보세요. Rust 2018부터 대여는 닫는 중괄호가 아니라 마지막으로 쓰인 곳에서 끝나요. 이걸 논-렉시컬 라이프타임(non-lexical lifetimes)이라고 해요. 그러니 벡터를 바꾸기 전에 참조자를 다 쓰면 돼요.
fn main() { let mut v = vec![1, 2, 3]; let first = &v[0]; println!("First: {first}"); // `first`를 마지막으로 써요. 대여가 여기서 끝나요 v.push(4); // ✅ 이제 읽는 사람이 없어요 println!("Now: {:?}", v);}실행 결과:
First: 1Now: [1, 2, 3, 4]방법 2: 값을 복사해 두기
섹션 제목: “방법 2: 값을 복사해 두기”값을 바꾼 뒤에도 예전 값이 필요할 때 이 방법을 고르세요. 참조자 대신 복사본을 가져오는 거예요. 숫자라면 &만 빼면 되고, String이라면 .clone()을 쓰세요.
fn main() { let mut queue = vec![String::from("deploy"), String::from("test")]; let next = queue[0].clone(); // 참조자가 아니라 나만의 복사본이에요 queue.push(String::from("release")); println!("다음 작업: {next}"); println!("대기열: {:?}", queue);}실행 결과:
다음 작업: deploy대기열: ["deploy", "test", "release"]Vec<i32>에서 let first = v[0];이라고 쓰면 공짜로 복사본이 생겨요. i32는 Copy이기 때문이에요.
방법 3: iter_mut이나 retain으로 그 자리에서 바꾸기
섹션 제목: “방법 3: iter_mut이나 retain으로 그 자리에서 바꾸기”for p in prices.iter() 같은 반복문을 쓰고 그 안에서 prices.remove(i)를 호출하거나 요소를 바꾸려고 했다면 이 방법을 고르세요. 이것도 E0502가 나요. 표준 라이브러리에 딱 이런 경우를 위한 메서드가 있어요.
fn main() { let mut prices = vec![120, 45, 300, 80];
// 모든 요소를 그 자리에서 바꿔요 for p in prices.iter_mut() { *p = *p * 9 / 10; // 10% 할인 }
// 필요 없는 요소를 지워요 prices.retain(|&p| p >= 50);
println!("{:?}", prices);}실행 결과:
[108, 270, 72]iter_mut은 요소마다 &mut를 하나씩 줘서, 쓰는 쪽이 언제나 하나뿐이에요. retain은 지우는 일을 한 번에, 안전하게 대신 해 줘요. 벡터와 반복자를 참고하세요.
방법 4: 바꿀 내용을 모은 뒤 적용하기
섹션 제목: “방법 4: 바꿀 내용을 모은 뒤 적용하기”읽은 내용을 바탕으로 요소를 추가해야 할 때 이 방법을 고르세요. 먼저 읽어서 모으고, 쓰기는 따로 한 번에 해요.
fn main() { let mut scores = vec![10, 25, 7, 40]; let bonuses: Vec<i32> = scores.iter().filter(|&&s| s > 20).map(|s| s * 2).collect(); scores.extend(bonuses); // 읽기 대여가 끝났으니 쓸 수 있어요 println!("{:?}", scores);}실행 결과:
[10, 25, 7, 40, 50, 80]Vec을 하나 더 만드니 메모리가 조금 더 들지만, 코드가 분명하고 누가 봐도 맞다는 걸 알 수 있어요. 보통은 괜찮은 거래예요.
방법 5: 인덱스 쓰기
섹션 제목: “방법 5: 인덱스 쓰기”다른 방법이 맞지 않을 때 마지막으로 쓰세요. 인덱스는 그냥 숫자일 뿐 대여가 아니라서, 값을 바꾸는 걸 막지 않아요.
fn main() { let mut scores = vec![10, 25, 7]; for i in 0..scores.len() { if scores[i] > 20 { scores.push(scores[i] * 2); } } println!("{:?}", scores);}실행 결과:
[10, 25, 7, 50]0..scores.len()은 반복문이 시작되기 전에 한 번만 계산되기 때문에, 새로 추가된 요소는 돌지 않아요. 인덱스로 요소를 지울 때는 조심하세요. 위치가 밀려서 요소를 건너뛰거나 패닉이 날 수 있어요. 지울 때는 retain(방법 3)이 더 안전해요.
자주 보게 되는 곳
섹션 제목: “자주 보게 되는 곳”Vec을 돌면서 그 Vec에 push할 때. for s in &scores { scores.push(..) }는 반복문 내내 scores를 빌리고 있어요. 해결: 먼저 모은 뒤 나중에 extend하거나(방법 4), 인덱스로 도세요(방법 5).
필드를 돌면서 &mut self 메서드를 호출할 때. impl 블록 안에서 아주 흔해요.
fn audit(&mut self) { for item in &self.items { self.record(item); // ❌ cannot borrow `*self` as mutable because it is also borrowed as immutable }}self.record는 self.items를 포함한 self 전체를 가변으로 빌려요. 해결: 다른 필드를 직접 건드리세요(self.log.push(item.clone())). Rust는 서로 다른 필드를 동시에 빌리는 건 허용하거든요. 아니면 항목들을 먼저 복제하세요. 구조체를 참고하세요.
HashMap에서 꺼낸 값을 들고 있으면서 삽입할 때. let apples = stock.get("apples").unwrap(); 다음에 stock.insert(..)를 하고 apples를 쓰면 E0502가 나요. 해결: let apples = *stock.get("apples").unwrap();이나 .copied()로 값을 복사해 오세요. HashMap을 참고하세요.
관련 에러
섹션 제목: “관련 에러”- E0499: 가변 대여 두 개가 동시에 있어요. 같은 개념인데, 읽는 쪽과 쓰는 쪽이 아니라 쓰는 쪽이 둘이에요.
- E0382: 값을 이동시킨 뒤에 또 썼어요. E0502를 대여 대신 이동으로 고치려다 자주 만나요.
- E0106: 참조자를 반환하는 함수에 라이프타임 지정자가 빠졌어요.
- 전체 컴파일러 에러: 에러 페이지 전체 목록이에요.
이해도 점검
섹션 제목: “이해도 점검”이해도 점검
1. let first = &v[0]; v.push(4); println!("{first}");는 왜 컴파일되지 않을까요?
2. Vec<i32>에서 50 미만인 요소를 모두 지우고 싶어요. E0502를 피하는 가장 깔끔한 방법은?
3. 요즘 Rust에서 불변 대여는 언제 끝날까요?
점수: 0 / 3
더 알아보기
섹션 제목: “더 알아보기”- E0502 — Rust error codes index (영어): 이 에러의 공식 설명이에요.
- 벡터에 여러 값의 목록 저장하기 — The Rust Programming Language 한국어판: 바로 이
let first = &v[0]; v.push(..)예제와 왜 실패하는지가 나와요. - 참조와 대여 — The Rust Programming Language 한국어판: “읽는 쪽은 여럿, 쓰는 쪽은 하나” 규칙을 자세히 설명해요.
- Non-lexical lifetimes — The Edition Guide (영어): 대여가 왜 이제 닫는 중괄호가 아니라 마지막 사용 지점에서 끝나는지 설명해요.
Vec— 표준 라이브러리 문서 (영어):retain,iter_mut,extend처럼 벡터를 안전하게 바꾸는 메서드들이 있어요.