Rust 에러 E0515: cannot return reference to local variable (지역 변수 참조 반환)
E0515는 함수가 자기가 직접 만든 무언가의 참조자(&)를 반환하려고 한다는 뜻이에요. 함수 안에서 만든 건 함수가 끝날 때 전부 정리돼요. 그러면 그 참조자는 아무것도 없는 곳을 가리키게 되죠. Rust 초보자라면 거의 누구나 이 에러를 만나요. 해결책은 대개 & 하나를 지우고 값 자체를 반환하는 거예요. (이 페이지는 Rust 컴파일러 에러 설명 시리즈의 하나예요.)
에러 메시지
섹션 제목: “에러 메시지”error[E0515]: cannot return reference to local variable `greeting` --> src/main.rs:3:5 |3 | &greeting // ❌ 함수가 끝나면 greeting은 해제돼요 | ^^^^^^^^^ returns a reference to data owned by the current function“지역 변수 greeting의 참조자는 반환할 수 없어요. 현재 함수가 소유한 데이터의 참조자를 반환하고 있어요”라는 뜻이에요.
이 에러가 나는 프로그램은 이거예요.
fn make_greeting(name: &str) -> &str { let greeting = format!("Hello, {name}!"); // 새 String, 이 함수가 소유해요 &greeting // ❌ 함수가 끝나면 greeting은 해제돼요}
fn main() { let text = make_greeting("Ferris"); println!("{text}");}같은 에러가 몇 가지 다른 문장으로 나오기도 해요. 뜻은 모두 같아요.
cannot return reference to local variable:&greeting이라고 썼을 때.cannot return value referencing local variable:greeting.as_str()이나&scores[2..]처럼 빌리는 메서드나 슬라이스를 썼을 때.cannot return value referencing temporary value:text.to_uppercase().as_str()처럼 데이터를 변수에 한 번도 담지 않았을 때(임시 값일 때).
어느 경우든 “returns a value referencing data owned by the current function”(현재 함수가 소유한 데이터를 참조하는 값을 반환한다)이라는 라벨이 붙어요.
무슨 뜻인가요
섹션 제목: “무슨 뜻인가요”greeting은 지역 변수예요. make_greeting 안에서 만들어지고, 이 함수가 소유하죠. 함수가 끝나면 지역 변수는 모두 해제(drop)돼요. 반환하려던 &str은 이제 존재하지 않는 String의 안쪽을 가리키게 되는 거예요.
이사를 나가면서 친구에게 집 열쇠를 건네는 것과 비슷해요. 열쇠는 진짜지만, 집은 이미 비었거나 다른 사람이 살고 있어요. 친구에게 필요한 건 열쇠가 아니라 가구예요.
이 에러에 이르는 흔한 흐름이 있어요. 먼저 &str을 반환하는 함수에서 greeting(String)을 그대로 반환해서 E0308(mismatched types)을 만나요. 그러면 컴파일러가 “consider borrowing here: &greeting”(여기서 빌려 보세요)라고 제안해요. 이 제안으로 타입은 맞춰지지만, 곧바로 E0515로 이어져요. 진짜 해결책은 반환 타입을 바꾸는 거예요.
Rust가 이렇게 하는 이유
섹션 제목: “Rust가 이렇게 하는 이유”참조자는 자신이 가리키는 값보다 절대 오래 살면 안 돼요. 지역 변수의 참조자를 반환하면 댕글링 참조(dangling reference), 즉 이미 해제된 메모리를 가리키는 포인터가 생겨요. C나 C++에서는 이게 컴파일되고 나중에 프로그램이 죽어요(더 나쁘면 조용히 엉뚱한 값을 돌려줘요). Rust는 이걸 컴파일할 때 잡아 줘요.
반환되는 참조자는 함수 바깥에서 살아 있는 무언가에서 빌려야 해요. 호출한 쪽이 넘겨준 매개변수, self의 필드, 문자열 리터럴 같은 'static 값이요. 함수 자신의 지역 변수는 여기에 해당하지 않아요. 그래서 라이프타임 명시로는 E0515를 고칠 수 없어요. 'a는 데이터가 얼마나 오래 사는지 설명할 뿐, 지역 변수를 더 오래 살게 만들 수는 없거든요. 소유권과 대여도 함께 읽어 보세요.
고치는 방법
섹션 제목: “고치는 방법”방법 1: 소유한 값을 반환하기
섹션 제목: “방법 1: 소유한 값을 반환하기”함수가 데이터를 만들어 낼 때 이 방법을 고르세요. 대부분은 이게 정답이에요. &str 대신 String을(또는 &[T] 대신 Vec<T>를) 반환하고 &를 지우세요. 값이 호출한 쪽으로 이동해서, 이제 호출한 쪽이 소유하게 돼요.
fn make_greeting(name: &str) -> String { let greeting = format!("안녕하세요, {name}!"); greeting // ✅ String을 통째로 호출한 쪽에 넘겨요}
fn top_scores() -> Vec<u32> { let mut scores = vec![42, 97, 63, 88]; scores.sort(); scores.split_off(2) // 가장 높은 두 점수를 새 Vec으로}
fn main() { let text = make_greeting("Ferris"); println!("{text}"); println!("{:?}", top_scores());}실행 결과:
안녕하세요, Ferris![88, 97]비용은 걱정하지 마세요. String이나 Vec을 반환할 때 옮겨지는 건 작은 포인터, 길이, 용량뿐이에요. 텍스트 자체는 복사되지 않아요. 마지막 줄에 그냥 format!("안녕하세요, {name}!")이라고 써도 돼요.
방법 2: 데이터를 매개변수로 받고 그 안을 가리키는 참조자 반환하기
섹션 제목: “방법 2: 데이터를 매개변수로 받고 그 안을 가리키는 참조자 반환하기”호출한 쪽이 이미 데이터를 소유하고 있고, 그중 일부만 가리키고 싶을 때 이 방법을 고르세요. 함수가 빌려 온 매개변수로 일한다면, 반환되는 참조자는 그 매개변수에서 빌린 거예요. 그 매개변수는 함수 바깥에서 살아 있죠.
fn top_two(scores: &mut [u32]) -> &[u32] { scores.sort(); &scores[scores.len() - 2..] // ✅ 호출한 쪽의 데이터를 가리켜요}
fn main() { let mut scores = vec![42, 97, 63, 88]; // main이 소유해요 let best = top_two(&mut scores); println!("{best:?}");}실행 결과:
[88, 97]참조자 매개변수가 하나면 Rust가 라이프타임을 알아서 채워 줘요. 둘 이상이면 'a가 필요할 수 있어요(E0106 참고). 이 방법은 입력의 일부를 반환할 때만 통해요. 입력으로 만든 완전히 새로운 값(to_uppercase(), format!, trim().to_string())은 다시 지역 값이니까 방법 1로 돌아가세요. 슬라이스도 참고하세요.
방법 3: 문자열 리터럴이라면 &'static str 반환하기
섹션 제목: “방법 3: 문자열 리터럴이라면 &'static str 반환하기”나올 수 있는 결과가 모두 코드에 적힌 고정된 텍스트일 때 이 방법을 고르세요. 문자열 리터럴은 프로그램 안에 저장되어 영원히 살아 있으니까, 그 참조자를 반환하는 건 언제나 안전해요.
fn status_text(code: u16) -> &'static str { match code { 200 => "OK", 404 => "Not Found", 500 => "Internal Server Error", _ => "Unknown", }}
fn main() { for code in [200, 404, 418] { println!("{code}: {}", status_text(code)); }}실행 결과:
200: OK404: Not Found418: Unknownformat!의 결과를 시그니처에 &'static str이라고 적어서 “고치려고” 하지는 마세요. String은 여전히 지역 값이라서 E0515가 다시 나올 뿐이에요.
방법 4: 빌릴 때도 있고 만들 때도 있다면 Cow 쓰기
섹션 제목: “방법 4: 빌릴 때도 있고 만들 때도 있다면 Cow 쓰기”함수가 보통은 입력을 그대로 돌려주지만, 가끔 새 문자열을 만들어야 할 때 이 방법을 고르세요. Cow(clone-on-write)는 빌린 &str이나 소유한 String 중 하나를 담아요. 그래서 꼭 필요할 때만 메모리를 할당해요.
use std::borrow::Cow;
fn safe_file_name(name: &str) -> Cow<'_, str> { if name.contains(' ') { Cow::Owned(name.replace(' ', "_")) // 필요할 때만 새 String } else { Cow::Borrowed(name) // 복사가 전혀 없어요 }}
fn main() { for name in ["report.txt", "my notes.txt"] { let clean = safe_file_name(name); let kind = match clean { Cow::Borrowed(_) => "빌린 값", Cow::Owned(_) => "소유한 값", }; println!("{clean} ({kind})"); }}실행 결과:
report.txt (빌린 값)my_notes.txt (소유한 값)Cow는 최적화 도구예요. 필요한지 잘 모르겠다면 String을 반환하세요(방법 1). 자세한 건 Weak와 Cow에 있어요.
자주 보게 되는 곳
섹션 제목: “자주 보게 되는 곳”지역 String의 s.as_str()을 반환할 때. greeting.as_str()은 greeting을 빌리니까 &greeting과 똑같이 실패해요. 이때 문장은 “cannot return value referencing local variable”이에요. 해결: greeting을 반환하고 반환 타입을 String으로 바꿔요.
새 문자열을 만드는 .map(). 겉보기엔 문제없어 보이지만, to_lowercase()가 매번 임시 String을 만들고, 그 값은 .as_str() 직후에 바로 해제돼요.
fn tags(input: &str) -> Vec<&str> { input.split(',').map(|t| t.trim().to_lowercase().as_str()).collect() // ❌ cannot return value referencing temporary value}해결: Vec<String>을 반환하고 .as_str()을 지워요. .map(|t| t.trim().to_lowercase()).collect()처럼요. (to_lowercase()가 없다면 t.trim()은 input의 슬라이스라서 Vec<&str>도 동작해요. 반복자를 참고하세요.)
필드를 포맷하는 메서드. fn full_name(&self) -> &str { &format!("{} {}", self.first, self.last) }는 “cannot return reference to temporary value”를 내요. 해결: -> String. &self.first를 반환하는 건 괜찮아요. 그 데이터는 메서드가 아니라 구조체 안에 살아 있으니까요. 구조체를 참고하세요.
참조자 매개변수가 없는 함수. fn make_greeting() -> &String은 그보다 먼저 E0106으로 실패해요. 빌려 올 수 있는 게 아무것도 없기 때문이에요. 해결책은 같아요. String을 반환하세요.
관련 에러
섹션 제목: “관련 에러”- E0106: missing lifetime specifier: 이 에러보다 먼저 나올 때가 많아요. 지역 변수의 참조자는
'a를 붙여도 해결되지 않아요. - E0597: borrowed value does not live long enough: 같은 문제가 한 함수 안에서, 반환 대신 블록이나 반복문으로 생기는 경우예요.
- E0308: mismatched types:
&str이라고 선언한 곳에서String을 반환할 때예요.&를 붙이지 말고 반환 타입을 바꾸세요. - E0382: borrow of moved value: 소유한 값으로 바꾼 뒤에는, 이동한 값을 다시 쓰지 않도록 조심하세요.
E0716: temporary value dropped while borrowed: 임시 값의 참조자를 같은 함수 안에서 나중에 쓰려고 할 때예요.- 모든 컴파일러 에러 설명
이해도 점검
섹션 제목: “이해도 점검”이해도 점검
1. 어떤 함수가 format!으로 메시지를 만들고 &message를 반환해요. 보통 올바른 해결책은 뭘까요?
2. 다음 중 컴파일되는 함수는?
3. Cow<str>는 언제 좋은 반환 타입일까요?
점수: 0 / 3
더 알아보기
섹션 제목: “더 알아보기”- E0515 — Rust error codes index (영어): 이 에러에 대한 공식 설명이에요.
- 참조와 대여 — The Rust Programming Language 한국어판: “댕글링 참조” 절의
dangle예제와, 왜String을 직접 반환하는 게 답인지 설명해요. - 라이프타임으로 참조자의 유효성 검증하기 — The Rust Programming Language 한국어판: 반환되는 참조자가 왜 매개변수 중 하나와 묶여야 하는지 다뤄요.
- 슬라이스 — The Rust Programming Language 한국어판: 입력에서 빌리는
&str슬라이스를 반환하는, 방법 2의 안전한 형태예요. Cow— 표준 라이브러리 문서 (영어): 방법 4에 나온 clone-on-write 타입과 예제예요.