Rust 에러 E0106: missing lifetime specifier (라이프타임 지정자 누락)
E0106은 이런 뜻이에요. 함수의 반환 타입이나 구조체에 참조자(&)를 썼는데, 그게 무엇을 빌린 건지 Rust가 알 수 없다. 로직에 버그가 있다는 말이 아니에요. 컴파일러가 여러분에게서 한 단어만 더 듣고 싶거나, 계획을 살짝 바꿔 주길 바라는 거예요. 거의 모든 사람이 Rust를 배운 첫 주에 이 에러를 만나요. 그리고 대부분 한 줄만 고치면 해결돼요. (이 페이지는 Rust 컴파일러 에러 설명 시리즈의 하나예요.)
에러 메시지
섹션 제목: “에러 메시지”error[E0106]: missing lifetime specifier --> src/main.rs:1:32 |1 | fn longer(a: &str, b: &str) -> &str { | ---- ---- ^ expected named lifetime parameter | = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `a` or `b`help: consider introducing a named lifetime parameter |1 | fn longer<'a>(a: &'a str, b: &'a str) -> &'a str { | ++++ ++ ++ ++“라이프타임 지정자가 없어요. 이 함수의 반환 타입은 빌린 값을 담고 있는데, 시그니처만 봐서는 그게 a에서 빌린 건지 b에서 빌린 건지 알 수 없어요”라는 뜻이에요. 마지막 help:는 이름 붙은 라이프타임 매개변수를 추가해 보라고 제안해요.
이 에러가 나는 프로그램은 이거예요.
fn longer(a: &str, b: &str) -> &str { if a.len() >= b.len() { a } else { b }}
fn main() { println!("{}", longer("apple", "fig"));}무슨 뜻인가요
섹션 제목: “무슨 뜻인가요”참조자는 대여(borrow)예요. 다른 누군가가 소유한 데이터를 가리키죠. 함수가 참조자를 반환할 때마다 Rust는 그게 누구의 데이터를 가리키는지 알아야 해요. 그래야 호출한 쪽이 결과를 쓰는 동안 그 데이터가 살아 있는지 확인할 수 있거든요.
1번 줄 아래의 표시 세 개를 보세요. 컴파일러는 a와 b에 ----를, 반환 타입에 ^를 그었어요. “빌린 &str을 반환하는데, 이게 a에서 왔을 수도 있고 b에서 왔을 수도 있잖아요. 어느 쪽이에요?“라고 묻는 거예요. 컴파일러는 추측하지 않아요.
도서관 대출증을 떠올려 보세요. 친구가 책을 건네면서 “이거 빌린 거야”라고 하면, 누구한테서 빌렸는지 알고 싶겠죠. 그래야 언제 돌려줘야 하는지 아니까요. 라이프타임(lifetime)이 바로 대여에 붙는 그 꼬리표예요.
Rust가 이렇게 하는 이유
섹션 제목: “Rust가 이렇게 하는 이유”Rust는 참조자가 자신이 가리키는 데이터보다 절대 오래 살지 않도록 보장해요(“댕글링 포인터”가 생기지 않아요). 함수 호출에서 이걸 확인할 때 컴파일러는 함수의 본문이 아니라 시그니처만 봐요. 그래서 반환되는 참조자가 어디서 왔는지를 시그니처 자체에 적어 줘야 해요.
평소에는 라이프타임을 거의 쓰지 않아요. Rust가 간단한 생략 규칙(elision rules) 세 가지로 알아서 채워 주거든요.
- 참조자 매개변수마다 각자의 라이프타임을 받아요.
- 참조자 매개변수가 딱 하나라면, 반환되는 참조자는 그 매개변수에서 빌려요.
&self나&mut self가 있는 메서드라면, 반환되는 참조자는self에서 빌려요.
E0106은 이 규칙들로 답이 나오지 않을 때 생겨요. 참조자 매개변수가 둘 이상이고 self가 없을 때, 참조자 매개변수가 하나도 없을 때, 또는 구조체에 참조자를 넣었을 때예요. 라이프타임 강좌에서 자세히 다루고, 참조자 자체는 대여에서 설명해요.
고치는 방법
섹션 제목: “고치는 방법”방법 1: 이름 붙은 라이프타임('a) 추가하기
섹션 제목: “방법 1: 이름 붙은 라이프타임('a) 추가하기”함수가 정말로 입력 중 하나의 일부를 반환할 때 이 방법을 고르세요. 'a는 그냥 이름이에요. 입력과 출력에 똑같이 적으면 “결과는 이 입력들이 살아 있는 동안 살아 있다”는 뜻이 돼요.
// 'a: 반환되는 &str은 a나 b에서 빌리니까, 둘이 살아 있는 동안만 살아 있어요.fn longer<'a>(a: &'a str, b: &'a str) -> &'a str { if a.len() >= b.len() { a } else { b }}
fn main() { let first = String::from("apple"); let second = String::from("fig"); println!("더 긴 단어: {}", longer(&first, &second));}실행 결과:
더 긴 단어: apple여기서 단어를 영어로 둔 건 .len()이 글자 수가 아니라 바이트 수를 비교하기 때문이에요. 한글은 한 글자가 3바이트라서 결과가 헷갈릴 수 있어요.
결과가 언제나 매개변수 하나에서만 나온다면, 그 매개변수에만 'a를 붙이세요. 예를 들면 fn lookup<'a>(map: &'a HashMap<String, String>, key: &str) -> Option<&'a str>처럼요. 컴파일러의 제안은 모든 곳에 'a를 붙이는데, 동작은 하지만 필요 이상으로 엄격해요.
방법 2: 소유한 String을 반환하기
섹션 제목: “방법 2: 소유한 String을 반환하기”함수가 새 텍스트를 만들 때, 또는 호출한 쪽이 대여에 묶이지 않았으면 할 때 이 방법을 고르세요. 소유한 String은 호출한 쪽의 것이 되니까 추적할 게 없어요.
fn greeting(name: &str, lang: &str) -> String { if lang == "ko" { format!("안녕하세요, {name}님!") } else { format!("Hello, {name}!") }}
fn main() { println!("{}", greeting("지민", "ko")); println!("{}", greeting("Sam", "en"));}실행 결과:
안녕하세요, 지민님!Hello, Sam!작은 메모리 할당이 한 번 생기지만, 대부분의 프로그램에서는 전혀 문제가 되지 않아요. 부담 갖지 않아도 돼요.
방법 3: 구조체에는 소유한 데이터 넣기
섹션 제목: “방법 3: 구조체에는 소유한 데이터 넣기”title: &str 같은 구조체 필드도 같은 에러를 내요.
error[E0106]: missing lifetime specifier --> src/main.rs:2:12 |2 | title: &str, | ^ expected named lifetime parameter구조체라면 거의 항상 데이터를 소유하는 게 가장 좋은 해결책이에요. &str 대신 String을, &[T] 대신 Vec<T>를 쓰세요. 그러면 구조체를 원하는 만큼 오래 쓸 수 있어요.
#[derive(Debug)]struct Book { title: String, // &str이 아니라 소유한 String pages: u32,}
fn main() { let book = Book { title: String::from("Dune"), pages: 412 }; println!("{} ({}쪽)", book.title, book.pages);}실행 결과:
Dune (412쪽)구조체에 라이프타임을 주는 건 그 구조체가 다른 곳에 있는 데이터를 잠깐 들여다보는 뷰일 때만이에요. 큰 텍스트를 훑어보는 파서 같은 경우죠. 그럴 때는 구조체와 필드에 <'a>를 적어요.
struct Excerpt<'a> { text: &'a str, // 아래의 novel에서 빌려요}
fn main() { let novel = String::from("Call me Ishmael. Some years ago..."); let first = novel.split('.').next().unwrap(); let excerpt = Excerpt { text: first }; println!("발췌: {}", excerpt.text);}실행 결과:
발췌: Call me Ishmael방법 4: 문자열 리터럴에는 'static 쓰기
섹션 제목: “방법 4: 문자열 리터럴에는 'static 쓰기”함수에 참조자 매개변수가 하나도 없으면, 에러는 “빌려 올 값이 없다(there is no value for it to be borrowed from)“고 말해요.
error[E0106]: missing lifetime specifier --> src/main.rs:1:22 |1 | fn default_name() -> &str { | ^ expected named lifetime parameter | = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from"OK" 같은 문자열 리터럴만 반환한다면 &'static str이라고 쓰세요. 리터럴은 프로그램 안에 박혀 있어서 프로그램이 실행되는 내내 살아 있어요.
fn status_text(code: u16) -> &'static str { match code { 200 => "OK", 404 => "Not Found", _ => "Unknown", }}
fn main() { println!("200의 의미: {}", status_text(200)); println!("404의 의미: {}", status_text(404));}실행 결과:
200의 의미: OK404의 의미: Not Found'static은 진짜 리터럴과 상수에만 쓰세요. 텍스트를 실행 중에 만든다면(format!으로 만들거나 파일에서 읽는다면) 'static은 도움이 안 돼요. 그럴 땐 String을 반환하세요(방법 2).
자주 보게 되는 곳
섹션 제목: “자주 보게 되는 곳”- 입력이 둘인 도우미 함수:
fn lookup(map: &HashMap<String, String>, key: &str) -> Option<&str>같은 경우예요. 해결:'a로 출력을map에만 묶어요(방법 1). - 텍스트를 담는 구조체: 파싱한 입력으로 만드는
User { name: &str }같은 경우예요. 해결:String필드를 써요(방법 3). - 정해진 메시지를 반환하는 함수:
fn error_label() -> &str같은 경우예요. 해결:&'static str(방법 4).
메서드는 좋은 소식이에요. fn name(&self) -> &str은 규칙 3 덕분에 라이프타임이 전혀 필요 없어요.
관련 에러
섹션 제목: “관련 에러”- E0382: borrow of moved value: 또 다른 소유권 에러로, 값을 넘겨준 뒤에 다시 쓰려고 할 때 나요.
- E0502: 불변으로 빌린 상태에서 가변 대여: 라이프타임을 제대로 적은 다음에 대여 검사기(borrow checker)가 일하는 모습이에요.
- E0499: 가변 대여를 두 번: 같은 데이터에
&mut이 동시에 두 개 있을 때예요. - E0308: mismatched types: 예를 들어
&str을 반환한다고 적은 함수에서String을 반환할 때예요. E0515: cannot return reference to local variable: 함수 안에서 만든 데이터에 방법 1을 써 보려 한 경우예요. 대신 소유한String을 반환하세요(방법 2).- 모든 컴파일러 에러 설명
이해도 점검
섹션 제목: “이해도 점검”이해도 점검
1. fn first_word(s: &str) -> &str은 왜 라이프타임 없이도 컴파일될까요?
2. 구조체 Config { path: &str }에서 E0106이 나요. 보통 가장 좋은 해결책은 뭘까요?
3. 어떤 함수가 format!으로 메시지를 만들어 반환해요. 반환 타입은 무엇이어야 할까요?
점수: 0 / 3
더 알아보기
섹션 제목: “더 알아보기”- E0106 — Rust error codes index (영어): 이 에러에 대한 공식 설명이에요.
- 라이프타임으로 참조자의 유효성 검증하기 — The Rust Programming Language 한국어판:
longest예제와 참조자를 담는 구조체까지, 라이프타임의 전체 이야기를 다뤄요. - Lifetime elision — The Rust Reference (영어): Rust가 라이프타임을 대신 채워 넣을 때 쓰는 정확한 규칙이에요.
- Elision — Rust by Example (영어): 라이프타임을 생략해도 되는 경우를 짧은 예제로 보여 줘요.
- Lifetimes — Rust by Example (영어): 함수, 메서드, 구조체에서 라이프타임을 직접 적는 예제를 실행해 볼 수 있어요.