Rust 에러 E0716: temporary value dropped while borrowed (임시 값이 먼저 해제됨)
E0716은 이런 뜻이에요. 변수 이름이 없는 값을 대여해 놓고, 그 대여를 다음 줄에서도 계속 썼어요. Rust는 이름 없는 값을 문장이 끝날 때 버리기 때문에, 대여가 아무것도 없는 곳을 가리키게 돼요. 무섭게 생겼지만, 가장 고치기 쉬운 대여 에러 중 하나예요. 보통은 한 줄을 두 줄로 나누기만 하면 돼요. (이 페이지는 Rust 컴파일러 에러 모음의 일부예요.)
에러 메시지
섹션 제목: “에러 메시지”error[E0716]: temporary value dropped while borrowed --> src/main.rs:2:13 |2 | let s = String::from("hi").as_str(); // s가 이름 없는 String을 대여해요 | ^^^^^^^^^^^^^^^^^^ - temporary value is freed at the end of this statement | | | creates a temporary value which is freed while still in use3 | println!("{s}"); // ❌ 그 String은 이미 사라졌어요 | - borrow later used here |help: consider using a `let` binding to create a longer lived value |2 ~ let binding = String::from("hi");3 ~ let s = binding.as_str(); // s가 이름 없는 String을 대여해요 |“대여 중에 임시 값이 해제됐다”는 뜻이에요. 표시는 “아직 쓰이는 중인데 해제되는 임시 값을 만듦”, “이 문장이 끝날 때 임시 값이 해제됨”, “대여가 나중에 여기서 쓰임”이고, help:는 “let 바인딩으로 더 오래 사는 값을 만들어 보라”고 제안해요.
이 에러를 만드는 프로그램은 이거예요.
fn main() { let s = String::from("hi").as_str(); // s가 이름 없는 String을 대여해요 println!("{s}"); // ❌ 그 String은 이미 사라졌어요}무슨 뜻인가요
섹션 제목: “무슨 뜻인가요”임시 값(temporary)은 식 중간에 만들어지지만 변수에 저장되지 않는 값이에요. 위 줄에서 String::from("hi")는 진짜 String을 만들지만, 그걸 소유하는 쪽이 없어요. 그 값을 받는 let name = ...이 없거든요. Rust는 임시 값을 문장이 끝날 때, 즉 ;에서 해제(drop, 정리)해요.
세 표시를 왼쪽에서 오른쪽으로 읽어 보세요.
String::from("hi")아래의^^^^: 여기서 임시String이 만들어져요.- 줄 끝의
-: 여기,;에서 그String이 해제돼요. - 3번째 줄
s아래의-: 그런데s(그String안을 가리키는&str)는 여기서도 아직 쓰여요.
주문을 포스트잇에 적었다가 자리를 뜨자마자 포스트잇을 버리는 웨이터를 떠올려 보세요. 친구에게 “주문은 저 포스트잇에 적혀 있어”라고 말해도, 친구는 아무것도 찾지 못해요. 메모를 직접 갖고 있거나, 남아 있는 곳에 주문을 적어 둬야 해요.
메시지의 help: 부분이 대개 정답이에요. 변수를 하나 만들어서(rustc는 이걸 binding이라고 불러요) 값에 더 오래 사는 주인을 붙여 주세요.
Rust가 이렇게 하는 이유
섹션 제목: “Rust가 이렇게 하는 이유”E0597과 같은 규칙이에요. 참조자는 절대로 자기가 가리키는 값보다 오래 살아서는 안 돼요. 그렇지 않으면 해제된 메모리를 가리키는 댕글링 참조가 돼요. 차이는 딱 하나예요. E0597에서는 값에 이름이 있고 }에서 사라지지만, E0716에서는 값에 이름이 없고 ;에서 사라져요.
임시 값을 같은 문장 안에서 쓰는 건 전혀 문제없어요. println!("{}", String::from("hi").as_str());는 컴파일돼요. ; 전에 대여가 끝나니까요. 에러는 다음 줄에서도 대여가 필요할 때만 나요. 대여는 언제 끝날까요?와 라이프타임을 참고하세요.
도움이 되는 예외가 하나 있어요. 임시 값 수명 연장(temporary lifetime extension)이라고 해요. let에서 임시 값 바로 앞에 &(또는 &mut)를 붙이면, Rust가 조용히 그 임시 값을 변수만큼 오래 살려 둬요. 그래서 이 줄들은 컴파일돼요.
struct Label<'a> { text: &'a str,}
fn make_title() -> String { String::from("받은편지함")}
fn main() { let greeting = &String::from("안녕"); // ✅ 연장됨: let 안에 바로 & let queue = &mut vec![1, 2, 3]; // ✅ 연장됨 queue.push(4); let label = Label { text: &make_title() }; // ✅ 연장됨: 구조체 리터럴 안에 바로 & println!("{greeting} {queue:?} {}", label.text);}실행 결과:
안녕 [1, 2, 3, 4] 받은편지함이 연장은 메서드 호출이나 함수 호출을 통과하지 못해요. String::from("hi").as_str(), make_title().as_str(), vec![1, 2].iter(), shout(&make_title())은 모두 호출 안에서 임시 값을 대여하니까, 임시 값은 여전히 ;에서 사라지고 E0716이 나요. 모든 경우를 외우려고 하지 마세요. E0716을 보면 값에 이름을 붙여 주면 돼요.
고치는 방법
섹션 제목: “고치는 방법”방법 1: let으로 임시 값에 이름 붙이기
섹션 제목: “방법 1: let으로 임시 값에 이름 붙이기”거의 매번 이걸 고르세요. 컴파일러가 제안하는 방법이고, 대부분의 E0716에 대한 정답이에요. 먼저 값을 자기 변수에 저장하고, 다음 줄에서 그 변수를 대여하세요.
아주 흔한 예가 입력 한 줄을 나누는 경우예요. read_command()는 새 String을 돌려주고, .trim()과 .split(' ')은 그 문자열을 대여하고, Vec<&str>이 그 대여들을 들고 있어요.
let parts: Vec<&str> = read_command().trim().split(' ').collect(); // ❌ E0716Rust는 read_command()를 가리키면서 문장이 끝날 때 해제된다고 말해요. 이름을 붙여 주세요.
fn read_command() -> String { // 실제 프로그램이라면 stdin에서 한 줄을 읽어 올 거예요. String::from(" add milk eggs \n")}
fn main() { let line = read_command(); // 이제 String이 main이 끝날 때까지 살아요 let parts: Vec<&str> = line.trim().split(' ').collect(); println!("{parts:?}"); println!("명령: {}, 항목 수: {}", parts[0], parts.len() - 1);}실행 결과:
["add", "milk", "eggs"]명령: add, 항목 수: 2실제 입력에서도 똑같이 만나게 돼요. io::stdin().lines()에 lines.next().unwrap().unwrap().trim().split(' ').collect()를 쓰면 같은 이유로 E0716이 나요. 먼저 let line = lines.next().unwrap().unwrap();을 따로 한 줄로 쓰세요. 문자열을 참고하세요.
방법 2: 참조자 대신 소유한 값 갖고 있기
섹션 제목: “방법 2: 참조자 대신 소유한 값 갖고 있기”대여가 여러분이 만들 수 있는 어떤 변수보다도 오래 살아야 할 때 고르세요. 이리저리 넘기는 구조체 안이나, 반복문 안에서 채우는 Vec 같은 경우예요. 반복문 안의 let은 매 회차가 끝날 때 해제되니까, E0716이 E0597로 바뀔 뿐이에요.
잘못된 버전 두 개를 볼게요. 임시 값에서 얻은 &str을 담는 구조체와, 새 문자열의 대여를 넣는 반복문이에요.
let label = Label { text: make_title().as_str() }; // ❌ E0716
let mut names: Vec<&str> = Vec::new();for name in ["ferris", "corro"] { names.push(name.to_uppercase().as_str()); // ❌ E0716}해결 방법은 구조체와 Vec가 문자열을 소유하게 하는 거예요. &str 대신 String을 쓰고, .as_str()을 지우세요.
struct Label { text: String, // 소유한 값이라 라이프타임이 필요 없어요}
fn make_title() -> String { String::from("받은편지함")}
fn main() { let label = Label { text: make_title() }; // String이 Label 안으로 이동해요 println!("라벨: {}", label.text);
let mut names: Vec<String> = Vec::new(); for name in ["ferris", "corro"] { names.push(name.to_uppercase()); // Vec가 새 String을 하나씩 소유해요 } println!("{names:?}");}실행 결과:
라벨: 받은편지함["FERRIS", "CORRO"]초보자에게는 소유한 필드(String, Vec<T>)가 거의 항상 가장 쉬운 선택이에요. &str 필드가 있는 구조체는 라이프타임이 필요하고, 빌려 온 데이터보다 절대 오래 살 수 없어요. 참조자를 담는 구조체와 구조체를 참고하세요.
방법 3: 체인 구조 바꾸기
섹션 제목: “방법 3: 체인 구조 바꾸기”임시 값이 반복자 체인의 시작일 때 고르세요. 아래 줄들은 vec![...]로 Vec를 만들고, .iter()로 대여하고, 그 반복자(또는 결과)를 나중을 위해 남겨 둬요.
let doubled = &mut vec![1, 2, 3].iter().map(|n| n * 10); // ❌ E0716let best = vec![72, 95, 88].iter().max(); // ❌ E0716앞에 붙인 &mut는 도움이 안 돼요. 수명 연장은 반복자를 살려 두지만, .iter()가 대여하는 Vec는 살려 두지 않거든요. 빠져나갈 방법은 두 가지예요.
into_iter()를 써서 반복자가Vec를 소유하게 하세요(rustc도 이걸 제안해요). 그러면&mut도 필요 없고,let mut만 쓰면 돼요..copied(),.cloned(),.sum(), 숫자를 모으는.collect::<Vec<_>>()처럼 소유한 결과로 체인을 끝내세요. 그러면;뒤에 임시 값을 대여하는 게 남지 않아요.
fn main() { // 반복자가 Vec를 소유하니까, 임시 값을 대여하는 게 없어요 let mut doubled = vec![1, 2, 3].into_iter().map(|n| n * 10); println!("{:?}", doubled.next()); println!("{:?}", doubled.next());
// .copied()는 Option<&i32>를 대여가 없는 값인 Option<i32>로 바꿔요 let best = vec![72, 95, 88].iter().max().copied(); println!("최고: {best:?}");}실행 결과:
Some(10)Some(20)최고: Some(95)나중에 Vec가 다시 필요하다면 방법 1로 돌아가세요. let scores = vec![72, 95, 88];을 쓰고 scores.iter().max()를 하면 돼요. 반복자를 참고하세요.
자주 보게 되는 곳
섹션 제목: “자주 보게 되는 곳”체인 중간에서 Mutex를 잠글 때. let first = names.lock().unwrap().first();는 E0716을 내요. 잠금 가드(lock guard)가 임시 값이기 때문이에요. 가드가 해제되면 잠금도 풀리니까, first가 그 안을 계속 가리킬 수 없어요. 해결: let guard = names.lock().unwrap();을 쓰고 guard.first()를 하거나, .first().cloned()로 데이터를 한 번에 복사해 오세요. 공유 상태를 참고하세요.
참조자를 돌려주는 함수에 &임시 값을 넘길 때. fn shout(s: &str) -> &str이 있을 때 let t = shout(&make_title());은 E0716을 내요. 돌려받은 &str이 임시 String을 대여하는데, 수명 연장은 함수 호출을 통과하지 못하거든요. 해결: let title = make_title();을 쓰고 shout(&title)을 하세요.
함수에서 돌려줄 때. 참조자를 돌려주는 함수(-> &'static str 같은)의 마지막 줄에 make_title().as_str()이라고 쓰면, 대신 E0515(“cannot return value referencing temporary value”, 임시 값을 참조하는 값은 돌려줄 수 없음)가 나요. 해결: String을 돌려주세요.
관련 에러
섹션 제목: “관련 에러”- E0597: borrowed value does not live long enough: 이름이 있고
}에서 해제되는 값에서 생기는 같은 문제예요. - E0515: cannot return reference to local variable: 지역 값이나 임시 값의 대여를 함수에서 돌려줄 때 나요.
- E0106: missing lifetime specifier:
text: &str같은 구조체 필드에는 라이프타임이 필요해요.String이 더 나은 선택이라는 신호인 경우가 많아요. - E0502: mutable borrow while immutably borrowed: 또 다른 “대여가 아직 쓰이는 중” 에러예요. 값의 해제가 아니라 변경이 문제예요.
- E0382: borrow of moved value: 소유한 값으로 바꾼 뒤에는, 이동한 값을 다시 쓰지 않도록 조심하세요.
- 모든 컴파일러 에러 모음
이해도 점검
섹션 제목: “이해도 점검”이해도 점검
1. Rust에서 "임시 값"(temporary)이란 무엇인가요?
2. let s = String::from("hi").as_str(); 다음에 println!("{s}");를 쓰면 E0716이 나요. 보통은 어떻게 고치나요?
3. 임시 값 수명 연장 덕분에 컴파일되는 줄은 어느 것인가요?
점수: 0 / 3
더 알아보기
섹션 제목: “더 알아보기”- E0716 — Rust error codes index (영어): 이 에러에 대한 공식 설명과
let으로 고치는 방법이에요. - 참조와 대여 — The Rust Programming Language 한국어판: 참조자가 절대 댕글링되지 않는 이유를 포함해 대여 규칙을 설명해요.
- Temporary scopes — The Rust Reference (영어): 임시 값이 언제 해제되는지에 대한 정확한 규칙이에요.
- Temporary lifetime extension — The Rust Reference (영어): 어떤
let형태가 임시 값을 살려 두는지 예제와 함께 보여 줘요. - 라이프타임으로 참조자의 유효성 검증하기 — The Rust Programming Language 한국어판: 값이 사는 기간과 참조자가 쓰이는 기간을 Rust가 어떻게 비교하는지 설명해요.