Rust 에러 E0499: cannot borrow as mutable more than once at a time (가변 대여 두 번)
E0499 에러는 같은 값에 대한 가변 참조자(&mut) 두 개를 동시에 가지려고 했다는 뜻이에요. 가장 흔한 대여 검사기(borrow checker) 에러 중 하나인데, 고치는 건 대개 간단해요. 한 줄만 옮기면 될 때가 많아요. (다른 에러를 찾고 있나요? Rust 컴파일러 에러 해설을 보세요.)
에러 메시지
섹션 제목: “에러 메시지”error[E0499]: cannot borrow `balance` as mutable more than once at a time --> src/main.rs:5:20 |4 | let deposit = &mut balance; | ------------ first mutable borrow occurs here5 | let withdraw = &mut balance; | ^^^^^^^^^^^^ second mutable borrow occurs here6 |7 | *deposit += 50; | -------------- first borrow later used here“balance를 한 번에 두 번 이상 가변으로 빌릴 수 없다”는 뜻이에요. 라벨은 차례대로 “첫 번째 가변 대여가 여기서 일어남”, “두 번째 가변 대여가 여기서 일어남”, “첫 번째 대여가 나중에 여기서 쓰임”이라고 말해요.
이 에러를 내는 프로그램은 다음과 같아요.
fn main() { let mut balance = 100;
let deposit = &mut balance; let withdraw = &mut balance;
*deposit += 50; *withdraw -= 30;
println!("Balance: {balance}");}무슨 뜻인가요
섹션 제목: “무슨 뜻인가요”컴파일러가 세 곳을 가리키는데, 셋을 합치면 전체 이야기가 돼요.
- 4번째 줄:
deposit이balance의 가변 대여가 돼요. - 5번째 줄:
withdraw가balance의 두 번째 가변 대여가 되려고 해요. - 7번째 줄:
deposit을 5번째 줄 이후에도 써요. 그래서 두 번째 대여가 시작될 때 첫 번째 대여가 아직 살아 있어요.
세 번째 라벨이 중요해요. &mut 대여 두 개는 그 라이프타임이 겹칠 때만 문제가 돼요. deposit을 5번째 줄 전에 다 썼다면 에러가 나지 않아요.
“편집” 열쇠가 하나뿐인 공유 문서를 떠올려 보세요. 열쇠를 가진 사람만 문서를 고칠 수 있어요. Rust는 열쇠를 복제해 주지 않아요. 두 번째 사람은 첫 번째 사람이 열쇠를 돌려줄 때까지 기다려야 해요.
Rust가 이렇게 하는 이유
섹션 제목: “Rust가 이렇게 하는 이유”Rust의 대여 규칙은 이거예요. 어느 순간이든 읽는 쪽(&)은 여럿, 또는 쓰는 쪽(&mut)은 하나만 있을 수 있고, 쓰는 쪽이 둘 이상일 수는 없어요. &mut 참조자는 “내가 있는 동안에는 아무도 이 값을 건드릴 수 없다”는 약속이에요.
이 약속이 실제 버그를 막아 줘요. 두 코드가 같은 값을 동시에 바꿀 수 있다면, 한쪽이 Vec의 크기를 바꾸는 동안 다른 쪽은 예전 메모리를 가리키고 있을 수 있어요. 다른 언어에서는 이게 프로그램 충돌이나 조용한 데이터 손상으로 이어져요. 스레드 사이라면 데이터 경합(data race)이 되고요.
이 규칙 덕분에 컴파일러는 &mut가 살아 있는 동안 그게 데이터에 닿는 유일한 길이라고 확신할 수 있어요. 이 안전성을 실행 비용 없이 얻는 거예요. 대신 가끔은 두 대여가 실제로는 부딪히지 않는다는 걸 컴파일러에게 더 분명하게 알려 줘야 해요.
고치는 방법
섹션 제목: “고치는 방법”방법 1: 첫 번째 대여를 끝낸 뒤 두 번째 대여 시작하기
섹션 제목: “방법 1: 첫 번째 대여를 끝낸 뒤 두 번째 대여 시작하기”가장 먼저 시도해 보세요. E0499는 대부분 이걸로 해결돼요. 대여는 블록 끝이 아니라 마지막으로 쓰인 곳까지만 이어져요. 그러니 첫 번째 참조자로 할 일을 다 하고 나서 두 번째 참조자를 만들면 돼요.
fn main() { let mut balance = 100;
let deposit = &mut balance; *deposit += 50; // `deposit`을 마지막으로 써요. 대여가 여기서 끝나요
let withdraw = &mut balance; // ✅ 이제 아무도 빌리고 있지 않아요 *withdraw -= 30;
println!("잔액: {balance}");}실행 결과:
잔액: 120변수를 따로 만들 필요가 없을 때도 많아요. balance += 50; 다음에 balance -= 30;이라고 써도 똑같이 돼요.
방법 2: 겹치지 않는 부분을 빌리기
섹션 제목: “방법 2: 겹치지 않는 부분을 빌리기”가끔은 정말로 &mut 두 개가 동시에 필요한데, 둘이 데이터의 서로 다른 부분을 가리키는 경우가 있어요. 컴파일러가 이걸 늘 알아채지는 못하니까, 직접 보여 줘야 해요.
Vec이나 슬라이스의 두 요소라면, tasks[0]과 tasks[1]은 둘 다 tasks 전체를 빌려요. split_at_mut (영어)을 써서, 절대 겹치지 않는 두 반쪽으로 나누세요.
fn main() { let mut tasks = vec![String::from("write"), String::from("test")];
// Vec을 겹치지 않는 두 반쪽으로 나눠요 let (left, right) = tasks.split_at_mut(1); let first = &mut left[0]; let second = &mut right[0];
std::mem::swap(first, second); println!("{tasks:?}");}실행 결과:
["test", "write"](그냥 맞바꾸기만 할 거라면 tasks.swap(0, 1)이 더 간단해요. split_at_mut은 두 참조자로 실제 작업을 해야 할 때 쓰는 거예요.)
구조체에서는 p.name_mut()와 p.score_mut()처럼 각각 &mut self를 받는 메서드 두 개를 부를 때 같은 에러가 나요. 메서드를 호출할 때마다 구조체 전체를 빌리거든요. 대신 필드를 직접 빌리면, 컴파일러가 둘이 따로라는 걸 알아봐요.
struct Player { name: String, score: u32,}
fn main() { let mut p = Player { name: String::from("Ferris"), score: 0 };
// 메서드를 거치지 않고 필드를 직접 빌려요 let name = &mut p.name; let score = &mut p.score;
name.push_str(" the Crab"); *score += 10; println!("{}의 점수: {}점", p.name, p.score);}실행 결과:
Ferris the Crab의 점수: 10점방법 3: 참조자를 들고 있지 말고 인덱스 쓰기
섹션 제목: “방법 3: 참조자를 들고 있지 말고 인덱스 쓰기”전형적인 예가 있어요. iter_mut()으로 Vec을 돌면서 그 안에서 push도 하려는 경우예요. 반복자가 반복문 내내 &mut를 들고 있으니, push가 두 번째 &mut가 돼요.
fn main() { let mut queue = vec![1, 2, 3]; for job in queue.iter_mut() { *job *= 10; queue.push(*job); // ❌ E0499: `queue`는 이미 iter_mut()이 빌리고 있어요 }}반복문이 컬렉션 자체를 바꿔야 한다면, 대신 위치(인덱스)를 도세요. 인덱스는 그냥 숫자라서 아무것도 빌리지 않아요.
fn main() { let mut queue = vec![1, 2, 3];
// `queue` 안을 가리키는 참조자 대신 위치를 돌아요 for i in 0..queue.len() { queue[i] *= 10; let job = queue[i]; queue.push(job); }
println!("{queue:?}");}실행 결과:
[10, 20, 30, 10, 20, 30]0..queue.len()은 반복문이 시작되기 전에 한 번만 계산되기 때문에, 새로 추가된 요소는 다시 돌지 않아요. queue[i]의 대여는 각각 한 줄 동안만 이어져요.
방법 4: RefCell (최후의 수단)
섹션 제목: “방법 4: RefCell (최후의 수단)”RefCell은 대여 검사를 컴파일할 때가 아니라 실행할 때 해요. 가변 대여 두 개가 같은 순간에 살아 있지만 않으면, mut 변수 없이도 borrow_mut()을 호출할 수 있어요.
use std::cell::RefCell;
fn main() { let balance = RefCell::new(100); // 참고: `mut`가 필요 없어요
*balance.borrow_mut() += 50; // 빌리고, 바꾸고, `;`에서 대여가 끝나요 *balance.borrow_mut() -= 30;
println!("잔액: {}", balance.borrow());}실행 결과:
잔액: 120솔직하게 말하면, RefCell은 규칙을 없애 주는 게 아니라 나중에 검사할 뿐이에요. borrow_mut() 가드 두 개를 동시에 살려 두면, 프로그램은 컴파일되지만 실행할 때 패닉이 나요.
use std::cell::RefCell;
fn main() { let balance = RefCell::new(100);
let mut deposit = balance.borrow_mut(); let mut withdraw = balance.borrow_mut(); // 💥 `deposit`이 아직 살아 있어요
*deposit += 50; *withdraw -= 30;}thread 'main' (12345) panicked at src/main.rs:7:32:RefCell already borrowed“RefCell이 이미 대여 중”이라는 패닉이에요. 그러니 RefCell은 데이터가 정말로 공유될 때(예를 들어 Rc<RefCell<T>> 안에서), 그리고 방법 1–3이 맞지 않을 때만 꺼내 쓰세요. 컴파일러를 조용히 시키는 도구가 아니에요.
자주 보게 되는 곳
섹션 제목: “자주 보게 되는 곳”- 반복문 안에서
&mut저장하기.for반복문 안에서handles.push(&mut counter)를 하면 “mutably borrowed here in the previous iteration of the loop”(이전 반복에서 여기서 가변으로 대여됨)라는 에러가 나요. 반복할 때마다 같은 값에 대한&mut가 하나씩 늘어나기 때문이에요. 해결: 인덱스나 소유한 값을 저장하거나, 반복문 안에서 값을 직접 바꾸세요. HashMap에get_mut두 번 호출하기.let a = stock.get_mut("apples")다음에let p = stock.get_mut("pears")를 하면stock을 두 번 빌려요. 해결:get_mut을 다시 부르기 전에a를 다 쓰거나,stock.get_disjoint_mut(["apples", "pears"])로 둘을 한 번에 가져오세요.- 같은 변수를 바꾸는 클로저 두 개.
let mut inc = || count += 1;과let mut inc2 = || count += 2;는 각자 살아 있는 동안&mut count를 들고 있어요. 해결: 클로저를 하나만 쓰거나, 두 번째 클로저를 만들기 전에 첫 번째 클로저를 마지막으로 호출하세요. 클로저를 참고하세요.
관련 에러
섹션 제목: “관련 에러”- E0502: cannot borrow as mutable because it is also borrowed as immutable: 같은 개념인데, 대여 하나가 읽기 전용
&예요. - E0382: borrow of moved value: 값이 다른 곳으로 이동한 뒤에 그 값을 썼어요.
- E0106: missing lifetime specifier: 반환하는 참조자가 얼마나 오래 사는지 Rust가 알아야 해요.
- 전체 컴파일러 에러: 에러 페이지 전체 목록이에요.
이해도 점검
섹션 제목: “이해도 점검”이해도 점검
1. Rust는 언제 E0499를 낼까요?
2. let a = &mut v;를 만들고, a를 마지막으로 쓴 뒤, let b = &mut v;를 만들어요. 어떻게 될까요?
3. 같은 슬라이스의 서로 다른 요소에 대한 &mut 참조자 두 개를 얻게 해 주는 도구는?
점수: 0 / 3
더 알아보기
섹션 제목: “더 알아보기”- E0499 — Rust error codes index (영어): 이 에러의 공식 설명이에요. 최소한의 예제가 있어요.
- 가변 참조자 — The Rust Programming Language 한국어판: “
&mut는 한 번에 하나만” 규칙을 처음 소개하는 장이에요. - Splitting Borrows — The Rustonomicon (영어): 구조체 필드는 따로 빌릴 수 있는데 슬라이스 요소는 왜 안 되는지,
split_at_mut이 어떻게 동작하는지 설명해요. RefCell<T>와 내부 가변성 패턴 — The Rust Programming Language 한국어판: 대여 검사를 실행 시간으로 미루는 게 언제 옳은 선택인지 알려 줘요.