# Rust 라이프타임 완벽 가이드: 어노테이션, 생략 규칙, 면접 대비 2026 > Rust 라이프타임을 심층 분석합니다. 라이프타임 어노테이션, 생략 규칙, 빌림 검사기의 작동 원리를 실제 예제와 함께 학습하고 기술 면접에 대비합니다. - Published: 2026-08-27 - Updated: 2026-08-27 - Author: Anthony Fillion-Maillet - Reading time: 5 min --- Rust의 라이프타임은 참조가 얼마나 오래 유효한지 컴파일러가 추적하는 메커니즘입니다. 가비지 컬렉션 언어가 런타임에 메모리를 관리하는 것과 달리, Rust의 빌림 검사기는 컴파일 시점에 참조의 유효성을 검증합니다. 이를 통해 코드가 실행되기 전에 메모리 관련 버그의 상당 부분을 제거할 수 있습니다. > **면접 핵심 답변** > > Rust에서 라이프타임은 참조가 유효한 범위를 나타내는 컴파일 타임 구조입니다. 빌림 검사기는 라이프타임을 사용하여 참조가 참조하는 데이터보다 오래 살지 않도록 보장하며, 런타임 오버헤드 없이 댕글링 포인터를 방지합니다. ## 라이프타임이 메모리에서 의미하는 것 라이프타임은 값이 얼마나 오래 존재하는지가 아니라, 값에 대한 *참조*가 얼마나 오래 유효한지를 설명합니다. Rust의 모든 참조는 어노테이션이 생략되더라도 라이프타임을 가집니다. 컴파일러가 거부하는 다음 함수를 살펴봅니다. ```rust // dangling_reference.rs fn create_dangling() -> &String { let s = String::from("hello"); &s // ERROR: `s` is dropped at end of function } ``` String `s`는 함수 스코프 내에서만 존재합니다. 함수가 반환될 때 String의 메모리가 해제되므로, 그 참조를 반환하면 댕글링 포인터가 생성됩니다. 빌림 검사기는 이를 컴파일 시점에 감지합니다. 수정하려면 소유권 있는 값을 반환하거나 참조된 데이터가 함수 호출보다 오래 존속하도록 보장해야 합니다. ```rust // valid_return.rs // Option 1: Return owned value fn create_owned() -> String { String::from("hello") } // Option 2: Reference data that outlives the function fn first_word(s: &str) -> &str { s.split_whitespace().next().unwrap_or("") } ``` `first_word`에서 반환되는 `&str`은 입력 `s`에서 빌려오므로, `s`가 유효한 동안 유효합니다. 호출자가 입력의 라이프타임을 제어합니다. ## 라이프타임 어노테이션 구문과 의미 명시적 라이프타임 어노테이션은 `'a`, `'b` 등의 구문을 사용합니다. 이는 참조가 얼마나 오래 살아야 하는지에 대한 컴파일러 지시가 아니라, 여러 참조의 라이프타임 간 관계를 설명합니다. [Rust 레퍼런스](https://doc.rust-lang.org/reference/trait-bounds.html#lifetime-bounds)는 라이프타임 어노테이션을 참조가 서로 상대적으로 얼마나 오래 유효해야 하는지 제약하는 제네릭 매개변수로 정의합니다. ```rust // lifetime_annotations.rs // Both inputs and output share the same lifetime 'a fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } } fn main() { let string1 = String::from("long string"); let result; { let string2 = String::from("short"); result = longest(&string1, &string2); println!("Longest: {}", result); // Valid: both strings alive } // println!("{}", result); // ERROR: string2 dropped } ``` 어노테이션 `'a`는 컴파일러에게 다음을 알립니다: 반환되는 참조는 `x`와 `y` 라이프타임의 *교집합* 동안 유효합니다. `string2`가 더 짧은 라이프타임을 가지므로, `string2`가 드롭된 후에는 `result`를 사용할 수 없습니다. 여러 개의 서로 다른 라이프타임은 더 복잡한 관계를 표현합니다. ```rust // multiple_lifetimes.rs // Output tied only to first parameter's lifetime fn first_only<'a, 'b>(x: &'a str, _y: &'b str) -> &'a str { x } fn main() { let owned = String::from("owned"); let result; { let temporary = String::from("temporary"); result = first_only(&owned, &temporary); } // result still valid: only depends on owned println!("{}", result); } ``` ## Rust 2024의 라이프타임 생략 규칙 컴파일러는 어노테이션이 생략되었을 때 세 가지 생략 규칙을 적용하여 라이프타임을 추론합니다. [Rustonomicon](https://doc.rust-lang.org/nomicon/lifetime-elision.html)에 문서화된 이 규칙들은 안전성을 희생하지 않으면서 보일러플레이트를 줄입니다. > **세 가지 생략 규칙** > > 1. 각 입력 참조는 자체 라이프타임 매개변수를 받습니다 > 2. 입력 라이프타임이 정확히 하나만 있으면, 모든 출력 참조에 적용됩니다 > 3. `&self` 또는 `&mut self`가 있으면, 그 라이프타임이 모든 출력 참조에 적용됩니다 이 규칙들은 가장 일반적인 패턴을 처리합니다. ```rust // elision_examples.rs // Rule 1: Each input gets own lifetime fn takes_two(x: &str, y: &str) {} // Compiler reads: fn takes_two<'a, 'b>(x: &'a str, y: &'b str) {} // Rule 2: Single input lifetime propagates to output fn first_char(s: &str) -> &str { &s[0..1] } // Compiler reads: fn first_char<'a>(s: &'a str) -> &'a str // Rule 3: &self lifetime propagates to output impl Parser { fn peek(&self) -> &Token { &self.tokens[self.position] } // Compiler reads: fn peek<'a>(&'a self) -> &'a Token } ``` 생략이 실패하면 컴파일러는 명시적 어노테이션을 요구합니다. 이는 여러 입력에서 파생된 참조를 반환하는 함수에서 가장 자주 발생합니다. ```rust // elision_fails.rs // ERROR: Can't determine output lifetime fn ambiguous(x: &str, y: &str) -> &str { if x.len() > y.len() { x } else { y } } // FIX: Explicit annotation resolves ambiguity fn unambiguous<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } } ``` ## 구조체 라이프타임과 outlives 관계 참조를 포함하는 구조체는 빌린 데이터가 구조체보다 오래 살아야 함을 표현하기 위해 라이프타임 어노테이션이 필요합니다. ```rust // struct_lifetimes.rs struct Excerpt<'a> { text: &'a str, } impl<'a> Excerpt<'a> { // new borrows from input, so Excerpt can't outlive the source fn new(source: &'a str, start: usize, end: usize) -> Self { Excerpt { text: &source[start..end] } } // level() returns owned data, no lifetime in signature fn level(&self) -> u32 { self.text.len() as u32 / 10 } } fn main() { let novel = String::from("Call me Ishmael. Some years ago..."); let excerpt = Excerpt::new(&novel, 0, 16); println!("Excerpt: {}", excerpt.text); } // novel dropped, but excerpt already out of scope ``` `Excerpt<'a>`의 `'a`는 구조체의 유효성을 빌린 `text`의 라이프타임에 연결합니다. 소스 String이 드롭된 후 `Excerpt`를 사용하려고 하면 컴파일 오류가 발생합니다. 여러 참조를 가진 구조체에서는 각각 다른 라이프타임을 가질 수 있습니다. ```rust // multiple_struct_lifetimes.rs struct Comparison<'a, 'b> { baseline: &'a str, candidate: &'b str, } impl<'a, 'b> Comparison<'a, 'b> { fn new(baseline: &'a str, candidate: &'b str) -> Self { Comparison { baseline, candidate } } fn baseline_only(&self) -> &'a str { self.baseline } } ``` ## 'static 라이프타임과 사용 시점 `'static` 라이프타임은 프로그램 실행 전체 동안 존속하는 데이터를 나타냅니다. 문자열 리터럴은 바이너리에 내장되어 있으므로 이 라이프타임을 가집니다. ```rust // static_lifetime.rs let s: &'static str = "I live forever"; // Constants are implicitly 'static const CONFIG_VERSION: &str = "2.0.0"; // Thread-safe globals require 'static static COUNTER: AtomicU64 = AtomicU64::new(0); ``` 일반적인 패턴으로 `T: 'static` 바운드가 있으며, 개발자를 자주 혼란스럽게 합니다. 이 바운드는 `T`가 'static이 아닌 참조를 포함하지 않음을 의미하며, `T`가 참조여야 함을 의미하지 않습니다. ```rust // static_bound.rs use std::thread; fn spawn_task(data: T) { thread::spawn(move || { // data moved into thread, must live independently println!("Processing in thread"); }); } fn main() { let owned = String::from("owned data"); spawn_task(owned); // OK: String is 'static (owns its data) let reference = "borrowed"; // spawn_task(reference); // OK: &'static str } ``` 스레드는 스폰한 함수보다 오래 존속할 수 있으므로, 스레드로 이동되는 데이터는 스택 로컬 변수에 대한 참조를 포함해서는 안 됩니다. ## 일반적인 라이프타임 오류와 해결책 몇 가지 패턴이 개발자를 일관되게 어렵게 합니다. 이를 이해하면 디버깅이 가속화됩니다. **로컬 변수에 대한 참조 반환:** ```rust // error_local_ref.rs fn bad_split(text: &str, delimiter: char) -> (&str, &str) { let parts: Vec<&str> = text.split(delimiter).collect(); (parts[0], parts[1]) // OK: parts[i] borrow from text, not from Vec } fn truly_bad() -> &str { let local = String::from("local"); &local // ERROR: local dropped at end of function } ``` **라이프타임이 맞지 않는 구조체에 참조 저장:** ```rust // error_struct_lifetime.rs struct Cache { data: String, // view: &str, // ERROR: needs lifetime parameter } // Self-referential structs require unsafe or crates like ouroboros struct CacheWithView<'a> { data: String, view: Option<&'a str>, // Can't point to self.data safely } ``` 필드가 다른 필드를 참조하는 자기 참조 구조체는 unsafe 코드와 함께 `Pin`을 사용하거나 [ouroboros](https://docs.rs/ouroboros/) 같은 크레이트가 필요합니다. [Rust 스마트 포인터](/technologies/rust/interview-questions/smart-pointers) 모듈에서 이러한 고급 패턴을 다룹니다. **트레이트 구현의 라이프타임 바운드:** ```rust // trait_lifetime_bounds.rs trait Processor { fn process<'a>(&self, input: &'a str) -> &'a str; } struct Prefixer { prefix: String, } impl Processor for Prefixer { // Can't return &format!(...) - would be temporary fn process<'a>(&self, input: &'a str) -> &'a str { input // Must return input or part of it } } ``` ## 제네릭 라이프타임을 위한 고차 트레이트 바운드(HRTB) 고차 트레이트 바운드는 타입이 특정 라이프타임뿐 아니라 *모든* 라이프타임에 대해 트레이트를 만족해야 함을 표현하기 위해 `for<'a>` 구문을 사용합니다. ```rust // hrtb.rs use std::fmt::Debug; // F must be callable with any lifetime 'a fn apply_to_refs(f: F) where F: for<'a> Fn(&'a str) -> &'a str, { let owned = String::from("test"); let result = f(&owned); println!("{}", result); } fn identity(s: &str) -> &str { s } fn main() { apply_to_refs(identity); } ``` HRTB는 클로저를 받는 API와 다양한 라이프타임의 참조로 작동해야 하는 [Rust async/await](/blog/rust/rust-async-await-tokio-futures-concurrency) 에코시스템에서 자주 나타납니다. ## Rust 라이프타임 면접 질문 기술 면접에서는 다양한 깊이로 라이프타임 이해도를 조사합니다. 이러한 질문은 Rust 포지션에서 정기적으로 출제됩니다. **질문: 이 코드가 컴파일에 실패하는 이유는?** ```rust // interview_q1.rs fn get_str() -> &str { "hello" } ``` **답변:** 반환 타입에 명시적 라이프타임 어노테이션이 필요합니다. 문자열 리터럴은 `'static` 라이프타임을 가지지만 함수 시그니처가 이를 표현하지 않습니다. 수정: `fn get_str() -> &'static str`. **질문: 이 코드가 컴파일되는 이유와 안전한지 설명하시오:** ```rust // interview_q2.rs fn longest<'a>(x: &'a str, _y: &str) -> &'a str { x } ``` **답변:** 출력 라이프타임이 `x`에만 연결되어 있으므로 이 코드는 컴파일됩니다. 반환 값이 `_y`에 의존하지 않으므로 `_y` 매개변수는 어떤 라이프타임도 가질 수 있습니다. 이는 안전합니다: 반환되는 참조의 유효성은 오직 `x`의 라이프타임에만 의존합니다. **질문: 소유 데이터와 함께 참조를 저장하려고 하면 어떻게 되는가?** ```rust // interview_q3.rs struct Config<'a> { name: String, description: &'a str, } ``` **답변:** 이 패턴은 유효하지만 `Config`의 사용 방법을 제약합니다. 구조체는 `description`이 참조하는 것보다 오래 존속할 수 없습니다. 자기 자신을 참조해야 하는 소유 데이터의 경우, 두 필드 모두 `String`을 사용하거나 [Rust 소유권과 빌림](/blog/rust/rust-ownership-borrowing-demystified)에서 다루는 기법을 고려합니다. **질문: 라이프타임은 트레이트 객체와 어떻게 상호작용하는가?** ```rust // interview_q4.rs trait Formatter { fn format(&self, input: &str) -> String; } fn get_formatter<'a>() -> Box { // ... } ``` **답변:** 트레이트 객체는 기본적으로 암묵적인 `'static` 라이프타임 바운드를 가집니다. `Box`를 명시적으로 작성하면 트레이트 객체가 라이프타임 `'a`의 참조를 포함할 수 있습니다. 명시적 바운드가 없으면 `Box`는 `Box`과 같습니다. ## 라이프타임 변성: 공변성과 반변성 변성은 타입이 중첩될 때 라이프타임이 어떻게 관련되는지 결정합니다. Rust 참조는 다음 규칙을 따릅니다. - `&'a T`는 `'a`에 대해 **공변**: 더 긴 라이프타임이 더 짧은 것을 대체할 수 있음 - `&'a mut T`는 `T`에 대해 **불변**: 타입이 정확히 일치해야 함 - `fn(&'a T)`는 `'a`에 대해 **반변**: 더 짧은 라이프타임이 더 긴 것을 대체할 수 있음 ```rust // variance.rs fn covariant_example() { let s: &'static str = "static"; let r: &str = s; // OK: 'static lives longer than any 'a } fn invariant_example() { let mut vec: Vec<&'static str> = vec!["a"]; // let s = String::from("local"); // vec.push(&s); // ERROR: &s is not &'static str } ``` 변성의 이해는 참조를 받거나 반환하는 제네릭 API를 설계할 때 중요합니다. [Rust 트레이트와 제네릭](/blog/rust/rust-traits-generics-advanced-guide) 기사에서 고급 제네릭 패턴을 다룹니다. ## 실무에서의 라이프타임 어노테이션: 핵심 요점 - 라이프타임은 값의 존재가 아닌 참조의 유효성을 설명합니다. 빌림 검사기는 이를 사용하여 컴파일 시점에 댕글링 포인터를 방지합니다. - 생략 규칙은 대부분의 경우를 자동으로 처리합니다. 명시적 어노테이션은 여러 입력에서 파생된 참조를 반환할 때 필요합니다. - 구조체 라이프타임은 outlives 관계를 표현합니다: 참조를 포함하는 구조체는 해당 참조의 라이프타임으로 매개변수화되어야 합니다. - 제네릭에 대한 `'static` 바운드는 "'static이 아닌 참조를 포함하지 않음"을 의미하며 "참조여야 함"을 의미하지 않습니다. - 자기 참조 구조체는 `Pin`, unsafe 코드 또는 헬퍼 크레이트를 통한 특별한 처리가 필요합니다. - 면접 질문은 코드가 컴파일에 실패하는 이유와 라이프타임 어노테이션이 유효성 보장을 어떻게 변경하는지에 대한 이해에 초점을 맞춥니다. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/ko/blog/rust/rust-lifetimes-explained-annotations-elision-interview