[0]

다른 언어와 마찬가지로, Rust의 오브젝트들도 lifetime을 갖는다. 하지만 다른 언어들과 다르게, Rust에서는 오브젝트에 대한 레퍼런스의 lifetime에 대해 컴파일 타임에 체크하고, 컴파일 에러를 발생시킨다. 또한 lifetime이 모호한 경우에도 컴파일 에러가 발생하는데, 이를 방지하기 위해 lifetime parameter라는 Rust만의 독특한 문법을 활용해야 한다. 이 글에서는 Rust에서 가장 헷갈리는 개념 중 하나인 lifetime parameter에 대해 알아본다.

 

[1]

let p;

{
    let num = 10;
    p = #
}

println!("{}",p);

위 코드는 컴파일 에러가 발생한다. p가 num을 가리키도록 되어있는데, println이 호출되는 시점에 num의 lifetime이 만료되어 p가 dangling reference가 되었기 때문이다. 이런 상황에서 C와 같은 대부분의 언어는 컴파일 에러가 발생하지 않는다. 반면에, Rust는 각 레퍼런스마다의 lifetime을 추적하며, lifetime이 만료된 시점에서 접근할 경우 컴파일 에러를 발생한다. Rust 컴파일러의 이러한 기능을 Borrow checker라고 한다. 또한 컴파일 에러 메시지에서, Borrow checker가 어느 시점에 lifetime이 소멸되었는지 표시해주므로, 디버깅할 때 편리하다.

 

[2]

Borrow checker 덕분에 Rust에서는 각 레퍼런스의 lifetime을 컴파일 타임에 미리 알 수 있다. 하지만 단순 코드상으로 lifetime을 추론해내는 것이 불가능한 경우가 있을 수 있다. Rust book의 예시 코드를 보자.

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

위 함수에서는 리턴값이 x의 레퍼런스 일지, y의 레퍼런스 일지 알 수 없다. 따라서 컴파일러는 다음과 같은 에러를 띄우게 된다.

 --> src\main.rs:5:33
  |
5 | fn longest(x: &str, y: &str) -> &str {
  |                                 ^ expected lifetime parameter
  |
  = help: this function's return type contains a borrowed value, 
  but the signature does not say whether it is borrowed from `x` or `y`

이를 해결하기 위해선, lifetime을 명시해주어야 한다. 명시법은 제너릭 타입과 유사한데, 앞에 ' 만 붙이면 된다.

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

위 함수에서는 a라는 lifetime을 두 매개변수와 리턴값에 대해 명시해주었다. 여기서 주의해야 할 것이, lifetime parameter는 실제 lifetime에 영향을 끼치지 않는다. lifetime parameter는 컴파일러에게 x와 y 그리고 리턴값이 최소한 a만큼의 lifetime을 갖는다는 힌트를 줄 뿐이다. 즉 위 함수에서는 "리턴 값의 lifetime이 최소한 x와 y 중 lifetime이 작은 것만큼은 된다"라는 뜻이다. 다음의 코드를 통해 확인해 보자.

fn longest<'a>(x: &str, y: &str) -> &'a str {
    let result = String::from("really long string");
    result.as_str()
}

위 함수의 result는 longest함수 내의 지역 변수이고, 이를 리턴하는 것은 전형적인 dangling pointer의 발생 사례이기 때문이다. lifetime paremeter가 오브젝트의 lifetime을 변경하는 것이 아니므로, 위 함수는 컴파일 에러가 난다. 

 

더보기

[의문]

그런데 여기에서 한 가지 의문이 생긴다. 위의 함수에서, Rust 컴파일러는 x와 y의 lifetime을 이미 알고 있다. 그렇다면, 굳이 lifetime을 명시적으로 표기할 필요 없이, 컴파일러가 알아서 x와 y 중 더 작은 lifetime을 리턴 값에 대응시키면 되는 거 아닌가? 여기에 대한 해답은 사실 아직 잘 모르겠다.

 

[3]

대부분의 경우, lifetime parameter를 명시할 필요는 없다. 솔직히 함수 작성시마다 이것도 고려해야 된다면 얼마나 귀찮겠는가. Rust에서는 이런 귀찮음을 방지하고자 생략 가능한 규칙들이 존재한다. (생략 가능하다고 하더라도 lifetime을 명시해도 무방하다) 이 규칙은 크게 3가지로 구성되는데 다음과 같다.

1. 모든 레퍼런스는 lifetime을 갖는다.
2. 만약 인자로 입력받는 레퍼런스가 1개라면, 해당 레퍼런스의 lifetime이 모든 리턴값에 적용된다.
3. 함수가 self 혹은 &mut self를 인자로 받는 메소드라면, self의 lifetime이 모든 리턴값에 적용된다.

위 3가지 규칙을 통해, 인자와 리턴값 레퍼런스들의 모든 lifetime을 유추할 수 있다면, lifetime parameter를 명시하지 않아도 된다.

 

 

 

 

[참고]

https://blog.seulgi.kim/2019/12/rust-lifetime-elision.html

 

[Rust] 함수의 lifetime parameter는 언제 써야 하고 언제 생략할 수 있나요?

Seulgi Kim's programming blog.

blog.seulgi.kim

https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html

 

Validating References with Lifetimes - The Rust Programming Language

One detail we didn’t discuss in the “References and Borrowing” section in Chapter 4 is that every reference in Rust has a lifetime, which is the scope for which that reference is valid. Most of the time, lifetimes are implicit and inferred, just like most

doc.rust-lang.org

 

https://dev.to/cuichenli/understand-basic-lifetime-annotation-in-rust-5en9

 

Understand Basic Lifetime Annotation in Rust

My personal understanding of lifetime annotation in rust

dev.to

 

'Computer Science > Rust' 카테고리의 다른 글

Rust의 async/await와 Future  (0) 2020.07.21
Rust의 Copy trait와 Clone trait  (2) 2020.06.30
Rust의 스마트 포인터  (0) 2020.05.20
Rust의 Trait  (1) 2020.03.10
Rust에서 null을 도입하지 않은 이유  (0) 2020.01.17

+ Recent posts