자주 만나는 빌림 검사기 에러 읽는 법
이 챕터는 이론이 아니라 사전에 가깝습니다. Rust를 처음 3주 정도 써본 사람이 실제로 마주치는 에러 메시지 다섯 가지를 그대로 가져와서, 무슨 뜻인지와 어떻게 고치는지만 다룹니다. 에러 코드(E0502처럼)로 검색해서 이 챕터에 왔다면 바로 해당 절로 건너뛰어도 됩니다.
E0502: cannot borrow as mutable because it is also borrowed as immutable
fn main() {
let mut scores = vec![10, 20, 30];
let first = &scores[0];
scores.push(40); // 에러
println!("{first}");
}error[E0502]: cannot borrow `scores` as mutable because it is also
borrowed as immutablefirst가 scores의 한 원소를 가리키고 있는데, push는 벡터 전체를 가변으로 빌립니다. 벡터가 커지면서 내부적으로 메모리를 재할당할 수 있고, 그러면 first가 가리키던 주소가 무의미해집니다(댕글링 포인터). 컴파일러는 이 가능성 자체를 차단합니다.
고치는 법은 first를 더 이상 안 쓸 시점을 명확히 하는 겁니다.
fn main() {
let mut scores = vec![10, 20, 30];
let first = scores[0]; // 참조가 아니라 값을 복사 (i32는 Copy)
scores.push(40);
println!("{first}");
}&scores[0] 대신 scores[0]으로 값을 복사해오면 scores에 대한 빌림 자체가 사라지므로 문제가 없어집니다. 원소가 Copy가 아닌 타입이라면 .clone()을 쓰거나, 필요한 값을 먼저 다 읽고 나서 가변 작업을 하도록 순서를 바꿔야 합니다.
E0499: cannot borrow as mutable more than once at a time
fn main() {
let mut config = String::from("debug=true");
let r1 = &mut config;
let r2 = &mut config; // 에러
r1.push_str(";verbose=true");
r2.push_str(";log=info");
}가변 참조 두 개가 동시에 살아있는 전형적인 경우입니다. 앞 챕터에서 다룬 규칙 그대로입니다. 고치는 법은 하나를 다 쓰고 난 다음에 다음 참조를 만드는 것.
fn main() {
let mut config = String::from("debug=true");
let r1 = &mut config;
r1.push_str(";verbose=true");
let r2 = &mut config; // r1을 더 안 쓰므로 문제없음
r2.push_str(";log=info");
}E0382: borrow of moved value / use of moved value
fn main() {
let name = String::from("러스트");
let greeting = format!("안녕, {name}");
let backup = name; // move 발생
println!("{greeting}, {backup}, {name}"); // 에러: name은 이미 이동됨
}let backup = name;에서 name의 소유권이 backup으로 넘어갑니다. 그 뒤에 name을 다시 쓰면 에러입니다. 세 가지 해법이 있고, 상황에 따라 고르는 기준이 다릅니다.
name.clone()을 쓴다 — 가장 간단하지만 데이터를 복제하는 비용이 듭니다. 문자열이 작거나, 성능이 중요하지 않은 코드라면 이게 정답입니다.- 참조로 바꾼다 —
let backup = &name;처럼 애초에 소유권이 필요 없다면 빌려오면 됩니다. - 순서를 바꾼다 —
name을 다 쓴 다음에 옮긴다.
실무에서 겪는 대부분의 E0382는 사실 "옮길 필요가 없는데 옮겨서" 생깁니다. .clone()으로 일단 넘어가고, 나중에 프로파일링해서 진짜 병목이면 그때 구조를 손보는 게 현실적인 순서입니다. 처음부터 완벽한 소유권 설계를 하려다 지쳐서 Rust를 포기하는 사람을 많이 봤습니다.
E0515: cannot return reference to local variable
fn make_greeting() -> &String {
let greeting = String::from("안녕하세요");
&greeting // 에러
} // greeting은 여기서 drop됨error[E0515]: cannot return reference to local variable `greeting`함수 안에서 만든 값의 참조를 함수 밖으로 반환하려는 경우입니다. greeting은 함수가 끝나는 순간 drop되므로, 그 참조는 함수를 벗어나자마자 아무것도 안 가리키는 댕글링 참조가 됩니다. C에서 지역 변수의 주소를 반환하는 것과 똑같은 실수인데, Rust는 이걸 컴파일 타임에 잡아냅니다. 고치는 법은 참조 대신 소유권 자체를 반환하는 것.
fn make_greeting() -> String {
let greeting = String::from("안녕하세요");
greeting // 소유권을 그대로 넘김
}다섯 가지를 관통하는 공통 원인
이 에러들을 하나씩 뜯어보면 사실 원인은 항상 같습니다. "이 참조가 가리키는 데이터가 참조보다 먼저 없어질 수 있는가?" 컴파일러는 이 질문에 조금이라도 "그럴 수 있다"는 답이 나오면 무조건 거부합니다. 100번 중 99번 안전하더라도 1번 위험하면 막습니다. 이게 답답하게 느껴질 때도 있지만, 정확히 이 엄격함 때문에 Rust로 짠 코드에서는 이 클래스의 버그가 아예 나오지 않습니다.