콘텐츠로 이동

Rust 에러 E0597: borrowed value does not live long enough (빌린 값의 수명 부족)

E0597은 이런 뜻이에요. 참조자(&x)를 값 x 자체보다 더 오래 붙잡고 있었어요. 값은 자기 블록이 끝날 때 정리되는데, 블록 바깥의 무언가가 아직 그 참조자를 쓰려고 하는 거예요. 안쪽 블록, 반복문, 구조체에서 특히 자주 나오는 에러이고, 보통은 한 줄을 옮기거나 참조자 대신 소유한 값을 저장하면 해결돼요. (이 페이지는 Rust 컴파일러 에러 모음의 일부예요.)

error[E0597]: `x` does not live long enough
--> src/main.rs:5:13
|
4 | let x = 5;
| - binding `x` declared here
5 | r = &x; // r이 x를 대여해요
| ^^ borrowed value does not live long enough
6 | } // 여기서 x가 해제돼요
| - `x` dropped here while still borrowed
7 | println!("r: {r}"); // ❌ r은 아무것도 없는 곳을 가리키게 돼요
| - borrow later used here

“x가 충분히 오래 살지 않는다”는 뜻이에요. 표시는 차례대로 “여기서 x를 선언함”, “빌린 값이 충분히 오래 살지 않음”, “아직 대여 중인데 여기서 x가 해제됨”, “대여가 나중에 여기서 쓰임”이에요.

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

fn main() {
let r;
{
let x = 5;
r = &x; // r이 x를 대여해요
} // 여기서 x가 해제돼요
println!("r: {r}"); // ❌ r은 아무것도 없는 곳을 가리키게 돼요
}

네 개의 표시가 순서대로 이야기를 들려줘요.

  1. 4번째 줄: x가 안쪽 블록 { ... } 안에서 만들어져요.
  2. 5번째 줄: r이 x를 대여해요. 여기까지는 괜찮아요.
  3. 6번째 줄: 블록이 끝나서 x가 해제(drop, 정리)돼요. 그런데 대여는 아직 살아 있어요.
  4. 7번째 줄: r을 쓰는데, 가리키던 값은 이미 사라졌어요.

친구가 묵는 호텔 방 번호를 적어 둔다고 생각해 보세요. 친구가 묵는 동안은 쓸모가 있어요. 하지만 친구가 체크아웃하고 나면, 메모에 적힌 번호는 그대로 있어도 빈방이나 낯선 사람의 방으로 이어져요. Rust는 빈방으로 이어지는 메모를 갖고 있지 못하게 해요.

짧은 표시인 “borrowed value does not live long enough”(빌린 값이 충분히 오래 살지 않음)도 보게 될 거예요. 같은 에러이고, 제목 줄에서는 변수 이름을 밝혀 줄 뿐이에요.

참조자는 절대로 자기가 가리키는 값보다 오래 살아서는 안 돼요. 그렇게 되면 댕글링 참조(dangling reference)가 생겨요. 이미 해제되어서 지금은 다른 무언가가 들어 있을 수도 있는 메모리를 가리키는 포인터예요. C와 C++에서는 이게 프로그램이 죽거나 보안 버그가 생기는 대표적인 원인이에요(“use after free”, 해제 후 사용).

Rust의 모든 값은 주인이 스코프를 벗어날 때 해제돼요. 보통은 값을 선언한 블록의 닫는 }에서요. 대여 검사기(borrow checker)는 두 구간을 비교해요. 값이 얼마나 오래 사는지와 참조자가 얼마나 오래 쓰이는지예요. 값의 } 뒤에서 참조자가 쓰이면 E0597이 나요. 스코프, 대여, 라이프타임을 참고하세요.

컴파일러가 불평하지 않는 부분도 눈여겨보세요. 5번째 줄에서 x를 대여하는 건 문제가 아니에요. 문제는 7번째 줄의 나중 사용뿐이에요. 마지막 줄을 지우면 프로그램은 컴파일돼요. 이게 아래 해결 방법들의 힌트가 돼요. 값을 더 오래 살게 하거나, 참조자의 사용을 더 일찍 끝내면 되는 거예요.

방법 1: 값을 바깥 스코프로 옮기기

섹션 제목: “방법 1: 값을 바깥 스코프로 옮기기”

값이 정말로 참조자만큼 오래 필요할 때 고르세요. 가장 흔한 해결 방법이에요. 값을 블록 앞에서(바깥에서) 선언해서, 적어도 참조자만큼은 오래 살게 하세요.

fn main() {
let scores = vec![72, 95, 88]; // 이제 main이 끝날 때까지 살아요
let best;
{
best = scores.iter().max().unwrap();
}
println!("최고 점수: {best}"); // ✅ scores가 아직 살아 있어요
}

실행 결과:

최고 점수: 95
▶ 직접 실행해 보기

경험칙: 주인은 빌리는 쪽보다 먼저 선언되어야 하고, 빌리는 쪽보다 오래 살아야 해요. 블록이나 반복문 안에서 만든 값의 참조자를 바깥에서 쓰고 있다면, 그 값을 바깥으로 옮기세요.

방법 2: 참조자 대신 소유한 값 저장하기

섹션 제목: “방법 2: 참조자 대신 소유한 값 저장하기”

반복문 안에서 만든 값들을 모을 때 고르세요. 반복문은 회차마다 새 String을 만들고 그 회차가 끝날 때 해제해요. 그래서 반복문 바깥의 Vec<&str>은 그 문자열들의 참조자를 갖고 있을 수 없어요.

fn main() {
let mut names: Vec<&str> = Vec::new();
for i in 1..=3 {
let name = format!("player{i}");
names.push(&name); // ❌ `name` does not live long enough
}
println!("{names:?}");
}

해결 방법은 Vec가 문자열을 소유하게 하는 거예요. Vec<&str>을 Vec<String>으로 바꾸고 String 자체를 넣으세요(값이 벡터 안으로 이동하니까, 벡터가 사는 동안 함께 살아요).

fn main() {
let mut names: Vec<String> = Vec::new();
for i in 1..=3 {
let name = format!("player{i}");
names.push(name); // String을 Vec 안으로 이동시켜요
}
println!("{names:?}");
}

실행 결과:

["player1", "player2", "player3"]
▶ 직접 실행해 보기

구조체와 HashMap에도 같은 생각이 적용돼요. 데이터가 컨테이너보다 작은 스코프에서 만들어진다면 &str(또는 &[T]) 대신 String(또는 Vec<T>)을 저장하세요. 초보자에게는 소유한 필드가 거의 항상 올바른 선택이에요. 문자열과 벡터를 참고하세요.

원본도 계속 써야 하거나, 원본을 대여만 하고 있을 때 고르세요. 참조자를 담는 구조체(Label<'a> { text: &'a str })는 자기가 가리키는 문자열보다 오래 살 수 없어요. 구조체에 소유한 String 필드를 주고, 복사본을 저장하세요.

struct Label {
text: String, // &str이 아니라 소유한 값
}
fn main() {
let label;
{
let title = String::from("받은편지함");
label = Label { text: title.clone() };
println!("블록 안: {title}");
}
println!("라벨: {}", label.text); // ✅ label은 자기만의 복사본을 갖고 있어요
}

실행 결과:

블록 안: 받은편지함
라벨: 받은편지함
▶ 직접 실행해 보기

나중에 title이 필요 없다면 복제하지 말고 이동시키세요. Label { text: title }처럼요. 짧은 문자열을 복제하는 건 비용이 적고 전혀 문제없어요. &str만 갖고 있다면 .to_string()으로 소유한 복사본을 만드세요.

방법 4: 대여가 제때 끝나도록 구조 바꾸기

섹션 제목: “방법 4: 대여가 제때 끝나도록 구조 바꾸기”

값이 원래 짧게 살아야 하는 경우에 고르세요. 값을 더 오래 살게 하는 대신, 값이 아직 살아 있는 동안 참조자를 쓰세요. 참조자를 돌려주는 함수에서 자주 나오는데, 유명한 longest 예제가 그래요.

fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() >= b.len() { a } else { b }
}
fn main() {
let first = String::from("Ferris the crab");
{
let second = String::from("Corro");
let winner = longest(&first, &second);
println!("가장 긴 이름: {winner}"); // ✅ second가 해제되기 전에 써요
}
}

실행 결과:

가장 긴 이름: Ferris the crab
▶ 직접 실행해 보기

이름을 영어로 둔 건 .len()이 글자 수가 아니라 바이트 수를 세기 때문이에요(한글 한 글자는 UTF-8에서 3바이트예요).

winner를 블록 바깥에서 선언하고 블록 뒤에서 출력하면, 우연히 first가 더 길더라도 second 때문에 E0597이 나요. 시그니처가 결과는 두 인자 중 어느 쪽이든 빌려 올 수 있다고 말하니까, 둘 다 결과보다 오래 살아야 하거든요. 나중에 결과가 정말 필요하다면 소유한 값으로 저장하세요. let winner = longest(&first, &second).to_string();처럼요.

블록 바깥에 저장한 클로저. name을 쓰는 클로저는 name을 대여해요. 클로저가 name보다 오래 살면 E0597이 나고, value captured here(여기서 값을 캡처함)라는 표시가 추가돼요.

let say_hello;
{
let name = String::from("Ferris");
say_hello = || println!("Hello, {name}!"); // ❌ `name` does not live long enough
}
say_hello();

해결: move || ...라고 써서 클로저가 name의 소유권을 가져가게 하세요. 클로저를 참고하세요.

스레드. thread::spawn(|| ...) 안에서 지역 변수를 대여하면 다른 에러인 E0373(“closure may outlive the current function”, 클로저가 현재 함수보다 오래 살 수 있음)이 나요. 스레드가 main보다 오래 실행될 수 있기 때문이에요. 해결: thread::spawn(move || ...), Arc로 공유하기, 또는 std::thread::scope를 쓰세요. 동시성을 참고하세요.

임시 값의 참조자. let label = Label { text: String::from("Inbox").as_str() };은 E0597과 비슷하지만 E0716, “temporary value dropped while borrowed”(대여 중에 임시 값이 해제됨)로 나와요. String이 변수에 저장되지 않아서 문장이 끝날 때 해제되거든요. 해결: 먼저 변수에 넣거나(let title = String::from("Inbox");), 필드를 String으로 바꾸세요.

함수에서 참조자를 돌려줄 때. 값이 함수 안에서 만들어졌다면 대신 E0515가 나요. 소유한 값을 돌려주세요.

이해도 점검

1. let r; { let x = 5; r = &x; } 뒤에 r을 println!으로 출력하면, 실제로 E0597을 일으키는 건 무엇인가요?

2. 반복문의 각 회차에서 String을 만들고, &name을 Vec<&str>에 넣어요. 보통은 어떻게 고치나요?

3. 스레드 클로저가 지역 Vec를 대여해요. 어떤 에러가 나나요?

점수: 0 / 3