러스트에는 (적어도 내가 아는)다른 언어에서는 찾아보기 힘든 라이프타임이란 개념이 있다.
라이프타임은 참조(Reference)가 유효한 범위를 의미한다. 기본적으로 러스트의 모든 참조는 라이프타임을 가지고 있고 구조체나 함수에 참조를 사용할 때 라이프타임 어노테이션을 이용해서 라이프타임을 명시를 해야 하지만 참조 변수의 라이프타임을 컴파일러가 추정 가능한 경우 생략할 수 있다.
라이프타임의 필요성
러스트는 메모리 안정성을 중요시하는 만큼 컴파일러단에서 dangling reference 를 차단한다. 러스트 컴파일러가 dangling reference 를 차단하기 위해 참조의 라이프타임을 관리한다. 참조의 라이프타임을 이용해서 참조가 가리키고 있는 인스턴스가 유효한 범위 내에서 참조가 사용될 수 있도록 한다. 이 과정을 borrow check 라고 한다.
러스트 컴파일러의 라이프타임 추론 규칙
참조가 함수의 파라매터로 사용될 때 러스터 컴파일러는 아래 규칙을 이용해서 참조의 라이프타임을 추론한다. 아래 규칙으로 라이프타임이 추론되지 않으면 라이프타임을 명시해야 한다.
- 각각의 파라매터는 각각의 라이프타임을 갖는다.
- 참조 파라메터가 하나만 있으면 함수의 반환값은 파라메터와 같은 라이프타임을 갖는다.
- 참조 파라메터가 여러 개이고 그중 하나가 &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 를 사용할 수 없다.
위 예제를 처음 봤을 때 어차피 좁은 라이프타임을 컴파일러가 선택한다면 굳이 왜 명시를 해야 하나 싶었다. 그런데 그 의문은 위와 같은 경우에 한정된 된 게 아닌가 싶다.