hyunsu blog

홈강좌Rust 입문·중급 47편 중 35편째

Rust 수명 — 돌려주는 참조가 누구에게서 왔는지 적는다 (E0106·E0597)

목차 · 4
  1. 01참조를 돌려주면 E0106
  2. 02<'a> 로 "둘 다 이만큼은 산다"
  3. 03약속보다 먼저 사라지면 E0597
  4. 04실습 — 구조체 안의 참조

Rust 수명(lifetime)은 참조가 얼마 동안 유효한지를 컴파일러에게 알리는 표시다. 참조 둘을 받아 하나를 돌려주는 함수는 어느 쪽에서 왔는지 알 수 없어 E0106 이 나고, <'a> 로 "둘 다 이만큼은 산다" 고 적는다. 그 약속보다 먼저 사라지는 값을 빌리면 E0597 이 난다.

01 참조를 돌려주면 E0106

두 문자열 중 긴 쪽을 돌려주는 함수다. 복사하지 않으려고 참조(&str, 20편)를 받고 참조를 돌려준다.

rust
fn longest(a: &str, b: &str) -> &str {
    if a.len() >= b.len() { a } else { b }
}

fn main() {
    println!("{}", longest("ferris", "rust"));
}
text
error[E0106]: missing lifetime specifier
 --> src/main.rs:1:33
  |
1 | fn longest(a: &str, b: &str) -> &str {
  |               ----     ----     ^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `a` or `b`
help: consider introducing a named lifetime parameter
  |
1 | fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {
  |           ++++     ++          ++          ++
  1. 무엇 — 수명 표시(lifetime specifier)가 빠졌다.
  2. 어디 — 반환 타입 &str 의 &. 첫 번째 help 가 이유를 말한다. 돌려주는 참조가 a 에서 왔는지 b 에서 왔는지 함수 첫 줄에 없다.
  3. 고치는 법 — 두 번째 help 가 <'a> 를 붙인 첫 줄을 통째로 보여 준다.

컴파일러는 돌려받은 참조를 쓰는 동안 원래 값이 살아 있는지 따져야 한다(18편의 빌림). 그런데 이 함수는 실행해 봐야 어느 쪽을 돌려줄지 알 수 있다. 그래서 사람이 첫 줄에 적어 줘야 한다.

02 <'a> 로 "둘 다 이만큼은 산다"

rust
fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {
    if a.len() >= b.len() { a } else { b }
}

fn main() {
    let first = String::from("ferris");
    let second = String::from("rust");
    println!("{}", longest(&first, &second));
}
text
   Compiling longest v0.1.0 (~/projects/longest)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.20s
     Running `target/debug/longest`
ferris
  • <'a> 는 32편의 <T> 처럼 이름을 선언한다. 작은따옴표로 시작하는 이름이 수명이다. 보통 'a 로 쓴다.
  • a: &'a str, b: &'a str 는 "두 참조 모두 적어도 'a 동안 유효하다" 이다.
  • -> &'a str 는 "돌려주는 참조도 'a 동안 유효하다" 이다.

합치면 "돌려받은 참조는 a·b 중 먼저 사라지는 쪽만큼 쓸 수 있다" 는 약속이다. 수명 표시는 값을 더 오래 살게 하지 않는다. 이미 있는 관계를 컴파일러에게 알려 줄 뿐이다.

03 약속보다 먼저 사라지면 E0597

second 를 블록 안에서 만들고, 블록 밖에서 결과를 써 보자.

rust
fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {
    if a.len() >= b.len() { a } else { b }
}

fn main() {
    let first = String::from("ferris");
    let result;
    {
        let second = String::from("crab");
        result = longest(&first, &second);
    }
    println!("{result}");
}
text
error[E0597]: `second` does not live long enough
  --> src/main.rs:10:34
   |
 9 |         let second = String::from("crab");
   |             ------ binding `second` declared here
10 |         result = longest(&first, &second);
   |                                  ^^^^^^^ borrowed value does not live long enough
11 |     }
   |     - `second` dropped here while still borrowed
12 |     println!("{result}");
   |                ------ borrow later used here
  1. 무엇 — second 가 충분히 오래 살지 않는다(does not live long enough).
  2. 어디 — 밑줄이 순서대로 말한다. 9번째 줄에서 만들고, 10번째 줄에서 빌렸고, 11번째 줄 } 에서 사라졌는데(dropped), 12번째 줄에서 그 빌림을 아직 쓴다.
  3. 고치는 법 — help: 는 없다. second 를 블록 밖에서 만들거나, println! 을 블록 안으로 옮긴다.

result 는 first 나 second 중 하나를 가리킨다. 실제로는 first(긴 쪽)이지만, 약속은 "먼저 사라지는 쪽만큼" 이었다. 사라진 값을 가리키는 참조가 생길 수 있으니 컴파일러가 막는다.

04 실습 — 구조체 안의 참조

구조체 칸에 참조를 넣을 때도 수명을 적는다. 책 문장의 첫 구절만 가리키는 구조체다.

rust
struct Excerpt<'a> {
    text: &'a str,
}

fn main() {
    let book = String::from("Call me Ferris. Some years ago...");
    let first = book.split('.').next().unwrap_or("");
    let quote = Excerpt { text: first };
    println!("{}", quote.text);
}
text
   Compiling excerpt v0.1.0 (~/projects/excerpt)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.20s
     Running `target/debug/excerpt`
Call me Ferris

struct Excerpt<'a> 와 text: &'a str 은 "이 구조체는 text 가 가리키는 문자열보다 오래 살 수 없다" 는 뜻이다. book 이 사라지면 quote 도 쓸 수 없다. split('.') 은 마침표로 문자열을 나누고, .next() 가 첫 조각을 Option 으로 준다(26편).

지금까지 수명을 안 적고도 참조를 잘 써 온 것은, 매개변수가 하나뿐이거나 메서드의 &self 처럼 답이 뻔한 경우를 컴파일러가 알아서 채웠기 때문이다. 큰따옴표 문자열의 타입은 정확히 &'static str 인데, 'static 은 "프로그램이 끝날 때까지 산다" 는 뜻이다. 다음 편은 이름 없는 작은 함수, 클로저다.