러스트에는 (적어도 내가 아는)다른 언어에서는 찾아보기 힘든 라이프타임이란 개념이 있다.

라이프타임은 참조(Reference)가 유효한 범위를 의미한다. 기본적으로 러스트의 모든 참조는 라이프타임을 가지고 있고 구조체나 함수에 참조를 사용할 때 라이프타임 어노테이션을 이용해서 라이프타임을 명시를 해야 하지만 참조 변수의 라이프타임을 컴파일러가 추정 가능한 경우 생략할 수 있다.

라이프타임의 필요성

러스트는 메모리 안정성을 중요시하는 만큼 컴파일러단에서 dangling reference 를 차단한다. 러스트 컴파일러가 dangling reference 를 차단하기 위해 참조의 라이프타임을 관리한다. 참조의 라이프타임을 이용해서 참조가 가리키고 있는 인스턴스가 유효한 범위 내에서 참조가 사용될 수 있도록 한다. 이 과정을 borrow check 라고 한다.

러스트 컴파일러의 라이프타임 추론 규칙

참조가 함수의 파라매터로 사용될 때 러스터 컴파일러는 아래 규칙을 이용해서 참조의 라이프타임을 추론한다. 아래 규칙으로 라이프타임이 추론되지 않으면 라이프타임을 명시해야 한다.

  1. 각각의 파라매터는 각각의 라이프타임을 갖는다.
  2. 참조 파라메터가 하나만 있으면 함수의 반환값은 파라메터와 같은 라이프타임을 갖는다.
  3. 참조 파라메터가 여러 개이고 그중 하나가 &self 혹은 &mut self 인 경우(method 인 경우) 반환 값은 self 와 같은 라이프타임을 갖는다.

예를 들어..

fn longest(x: &str, y: &str) -> &str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

fn main() { // scope a
    let x = ...
    let result;
    {   // scope b
        let y = ...
        result = longest(&x, &y);
    }
}

위 코드에서 x와 y는 다른 라이프타임을 가지고 있고 함수의 반환값은 x와 y의 값에 따라 런타임에 결정된다. 때문에 컴파일러는 result의 라이프타임을 결정할 수 없다. 이런 경우 프로그래머는 아래와 같이 라이프타임을 명시해서 컴파일러에게 알려주어야 한다.

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
...

컴파일러는 ‘a 를 x와 y의 라이프타임 중 더 좁은 범위로 해석한다. 위의 경우 longest 함수의 반환값인 result는 라이프타임이 더 짧은 y와 같은 라이프타임을 갖는다. 때문에 longest 의 반환값이 x 에 대한 참인 경우에도 scope b 밖에서는 result 를 사용할 수 없다.

위 예제를 처음 봤을 때 어차피 좁은 라이프타임을 컴파일러가 선택한다면 굳이 왜 명시를 해야 하나 싶었다. 그런데 그 의문은 위와 같은 경우에 한정된 된 게 아닌가 싶다.

참조