[Rust]

[Rust] 라이프타임(lifetime)

극꼼 2023. 8. 19. 02:14
반응형


<라이프타임(lifetime)>

: 어떤 참조자가 필요한 기간 동안 유효함을 보장합니다.

러스트의 모든 참조자는 라이프타임 범위를 가지며, 라이프타임은 암묵적으로 추론됩니다. 이 라이프타임을 명시해야 하는 경우에 대해 알아보겠습니다.

 

- 라이프 타임으로 댕글링 참조 방지

라이프타임의 주목적은 댕글링 참조(dangling reference) 방지입니다.

 

러스트 컴파일러는 대여 검사기(borrow checker)를 스코프로 비교해서 대여의 유효성을 판단합니다. 'b(참조 대상)가 'a(참조자)보다 짧기 때문에 이 코드는 컴파일되지 않습니다.

fn main() {
    let r;                // ---------+-- 'a //r의 라이프타임
                          //          |
    {                     //          |
        let x = 5;        // -+-- 'b  | // x의 라이프타임
        r = &x;           //  |       |
    }                     // -+       |
                          //          |
    println!("r: {}", r); //          |
}                         // ---------+

이 코드를 컴파일되도록 수정한 코드는 다음과 같습니다.

fn main() {
    let x = 5;            // ----------+-- 'b
                          //           |
    let r = &x;           // --+-- 'a  |
                          //   |       |
    println!("r: {}", r); //   |       |
                          // --+       |
}                         // ----------+

 

1) 함수에서의 제네릭 라이프타임

함수에서의 라이프타임 명시에 대한 필요성은 함수가 어떻게 동작하는지에 달려있습니다. 함수에서의 라이프타임 명시는 매개변수와 반환 값의 라이프타임을 연결하는 데에 있습니다. 라이프타임을 연결하면 러스트는 이를 통해 댕글링 포인터 생성을 방지하고, 메모리 안전 규칙을 위해바는 연산을 배제합니다.

 

다음은 문자열 슬라이스 두개를 비교해서 더 긴 쪽을 반환하는 함수입니다. 이 코드는 상황에 따라 반환할 참조자가 x가 될수도, y가 될수도 있는데 전달받은 참조자의 구체적인 라이프타임을 알 수 없기 때문에 다음과 같은 에러를 출력합니다 : error[E0106]: missing lifetime specifier

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

 

 

함수에 제네릭 라이프타임 매개변수를 명시하면 어떠한 라이프타임을 갖는 참조자라도 전달할 수 있습니다.

라이프타임 문법은 다음과 같습니다.

참조자 : &i32
명시적인 라이프타임이 있는 참조자 : &'a i32
명시적인 라이프타임이 있는 가변 참조자 : &'a mut i32

 

 

함수에서의 라이프타임은 본문이 아닌 시그니처에 적습니다. 위의 예시 코드에서 라이프타임 에러를 없애기 위해서는 다음과 같이 작성할 수 있습니다. 이는 함수가 가진 두 매개변수 둘 다 라이프타임 'a만큼 살아있는 문자열 슬라이스이고, 반환하는 문자열 슬라이스도 마찬가지로 라이프타임 'a만큼 살아있다는 정보를 가지고 있습니다.

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

 

longest() 함수를 다음과 같이 사용해서 컴파일할 경우 에러가 발생합니다 : error[E0597]: `str2` does not live long enough

result가 유효하기 위해서 대여 검사기가 result를 사용하는 시점에 str1, str2 둘 다 유효한지 확인합니다.

fn main(){
    let str1 = String::from("happy");
    
    let result; 
    {
        let str2 = String::from("cat");
        result = longest(str1.as_str(), str2.as_str());
    }
    println!("{}", result);
}

 

2) 구조체에서의 라이프타임

구조체가 참조자를 들고 있게 하기 위해서는 참조자에 라이프타임을 명시해야 합니다.

struct LifeTimeStruct<'a> {
    content: &'a str,
}

fn main(){
    let str1 = String::from("happy cat");
    let str2 = str1.split(' ').next().expect("Could not find ' '");
    let i = LifeTimeStruct {
        content: str2,
    };
    println!("{}", i.content); // happy 출력
}

 

 

- 라이프타임 생략

러스트는 몇가지 예측 가능한 패턴에 대해 라이프타임을 명시하지 않아도 대여 검사기가 추론할 수 있도록 합니다. 러스트는 이 패턴을 라이프타임 생략 규칙(lifetime elision rules)이라 부릅니다.

입력 라이프타임(input lifetime) : 함수나 메서드 매개변수의 라이프타임
출력 라이프타임(output lifetime) : 함수나 메서드의 반환 값의 라이프타임

 

컴파일러가 라이프타임을 알아내는 데 사용하는 규칙은 총 3가지가 있습니다. 첫번째는 입력 라이프타임에 적용되고, 두,세번째는 출력 라이프타임에 적용됩니다.

  1. 컴파일러가 참조자인 매개변수 각각에게 라이프타임 매개변수를 할당
  2. 만약 입력 라이프타임 매개변수가 하나라면 해당 라이프타임이 모든 출력 라이프타임에 대입
  3. 입력 라이프타임 매개변수가 여러개인데 그 중에 &self, &mut self가 있을 경우 self의 라이프타임이 모든 출력 라이프타임에 대입

이 세가지 규칙 덕분에 메서드 시그니처에서의 라이프타임은 생략될 수 있습니다.

위에서 예시로 사용했던 LifeTimeStruct의 content를 리턴하는 메서드를 구현해보겠습니다. 매개변수가 2개이지만 그 중 하나가 self이기 때문에 리턴값의 라이프타임에 self의 라이프타임이 대입되고, 메서드의 시그니처에 라이프타임을 명시하지 않아도 됩니다.

impl<'a> LifeTimeStruct<'a> {
    fn return_content(&self, str1: &str) -> &str {
        println!("Content is: {}", str1);
        self.content
    }
}

 

- 정적 라이프타임(static lifetime)

'static 라이프타임은 해당 참조자가 프로그램의 전체 lifetime동안 살아있음을 의미합니다.

모든 문자열 리터럴은 'static lifetime을 가집니다.


출처 : https://rust-kr.github.io/doc.rust-kr.org/ch10-03-lifetime-syntax.html

반응형

'[Rust]' 카테고리의 다른 글

[Rust] 트레이트(trait)  (0) 2023.08.18
[Rust] 제네릭 타입  (0) 2023.08.17
[Rust] 에러 처리  (0) 2023.08.16
[Rust] match 연산자  (0) 2023.08.15
[Rust] 컬렉션(Collection)  (0) 2023.08.14