Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

수명(Lifetimes) 심층 분석: 참조의 유효성 증명하기

학습 목표: Rust의 가장 정교한 시스템인 **수명(Lifetimes)**을 마스터합니다. 수명은 왜 필요한지, 함수와 구조체에서 어떻게 명시하는지 배우고, 'static 수명과 수명 생략 규칙(Elision Rules)을 통해 복잡한 빌림 관계를 해결하는 능력을 기릅니다.


1. 수명이 왜 필요한가? (The Why)

C#은 GC가 "참조가 하나라도 있으면 데이터를 살려두라"고 지시하지만, Rust는 GC 없이 컴파일 타임에 이를 판단해야 합니다. 수명은 **"이 참조가 가리키는 실제 데이터가 얼마나 오래 살아있는가?"**에 대한 컴파일러와의 약속입니다.

#![allow(unused)]
fn main() {
// [오류 상황] x와 y 중 누가 반환될지 모르므로 수명을 명시해야 함
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}
// 💡 'a는 "반환된 참조는 x와 y 중 더 짧게 사는 쪽보다 오래 살 수 없다"는 뜻입니다.
}

2. 수명 생략 규칙 (Elision Rules)

매번 수명을 적으면 코드가 너무 복잡해집니다. 그래서 Rust 컴파일러는 다음 세 가지 경우 수명을 자동으로 추론해 줍니다.

  1. 각 입력 참조는 고유한 수명을 가집니다.
  2. 입력 참조가 딱 하나라면, 출력 참조는 그 수명을 따릅니다.
  3. &self&mut self가 있다면, 출력 참조는 무조건 self의 수명을 따릅니다.

3. 구조체에서의 수명

데이터를 소유하지 않고 빌려오는 구조체는 반드시 수명을 명시해야 합니다. 구조체가 데이터보다 오래 살아서는 안 되기 때문입니다.

#![allow(unused)]
fn main() {
struct UserProfile<'a> {
    username: &'a str, // 실제 문자열은 이 구조체 밖의 어딘가에 있어야 함
}

impl<'a> UserProfile<'a> {
    fn announce(&self) {
        println!("사용자: {}", self.username);
    }
}
}

4. 'static 수명: 영원히 살아남는 것들

'static은 프로그램 실행 내내 유효한 수명을 뜻합니다.

  • 문자열 리터럴: 바이너리에 직접 포함되어 항상 존재합니다.
  • 전역 상수: conststatic으로 선언된 값들입니다.
  • 스레드 (spawn): 새 스레드로 데이터를 보낼 때, 호출자보다 스레드가 더 오래 살 수 있으므로 'static 수명(혹은 소유권 이전)이 요구됩니다.

💡 실무 팁: "수명이 너무 꼬인다면?"

수명 명시가 감당하기 힘들 정도로 복잡해진다면, 설계를 다시 검토할 때입니다.

  • 참조 대신 **.to_string()이나 .clone()**으로 데이터를 복사하여 소유권을 넘기세요.
  • **Rc<T>Arc<T>**와 같은 스마트 포인터를 사용하여 소유권을 공유하세요. 대부분의 비즈니스 로직은 복잡한 수명 없이도 충분히 구현 가능합니다.