홈강좌Rust 입문·중급 47편 중 35편째
Rust 수명 — 돌려주는 참조가 누구에게서 왔는지 적는다 (E0106·E0597)
Rust 수명(lifetime)은 참조가 얼마 동안 유효한지를 컴파일러에게 알리는 표시다. 참조 둘을 받아 하나를 돌려주는 함수는 어느 쪽에서 왔는지 알 수 없어 E0106 이 나고, <'a> 로 "둘 다 이만큼은 산다" 고 적는다. 그 약속보다 먼저 사라지는 값을 빌리면 E0597 이 난다.
01 참조를 돌려주면 E0106
두 문자열 중 긴 쪽을 돌려주는 함수다. 복사하지 않으려고 참조(&str, 20편)를 받고 참조를 돌려준다.
fn longest(a: &str, b: &str) -> &str {
if a.len() >= b.len() { a } else { b }
}
fn main() {
println!("{}", longest("ferris", "rust"));
}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 {
| ++++ ++ ++ ++- 무엇 — 수명 표시(lifetime specifier)가 빠졌다.
- 어디 — 반환 타입
&str의&. 첫 번째help가 이유를 말한다. 돌려주는 참조가a에서 왔는지b에서 왔는지 함수 첫 줄에 없다. - 고치는 법 — 두 번째
help가<'a>를 붙인 첫 줄을 통째로 보여 준다.
컴파일러는 돌려받은 참조를 쓰는 동안 원래 값이 살아 있는지 따져야 한다(18편의 빌림). 그런데 이 함수는 실행해 봐야 어느 쪽을 돌려줄지 알 수 있다. 그래서 사람이 첫 줄에 적어 줘야 한다.
02 <'a> 로 "둘 다 이만큼은 산다"
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));
} 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 를 블록 안에서 만들고, 블록 밖에서 결과를 써 보자.
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}");
}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- 무엇 —
second가 충분히 오래 살지 않는다(does not live long enough). - 어디 — 밑줄이 순서대로 말한다. 9번째 줄에서 만들고, 10번째 줄에서 빌렸고, 11번째 줄
}에서 사라졌는데(dropped), 12번째 줄에서 그 빌림을 아직 쓴다. - 고치는 법 —
help:는 없다.second를 블록 밖에서 만들거나,println!을 블록 안으로 옮긴다.
result 는 first 나 second 중 하나를 가리킨다. 실제로는 first(긴 쪽)이지만, 약속은 "먼저 사라지는 쪽만큼" 이었다. 사라진 값을 가리키는 참조가 생길 수 있으니 컴파일러가 막는다.
04 실습 — 구조체 안의 참조
구조체 칸에 참조를 넣을 때도 수명을 적는다. 책 문장의 첫 구절만 가리키는 구조체다.
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);
} Compiling excerpt v0.1.0 (~/projects/excerpt)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.20s
Running `target/debug/excerpt`
Call me Ferrisstruct Excerpt<'a> 와 text: &'a str 은 "이 구조체는 text 가 가리키는 문자열보다 오래 살 수 없다" 는 뜻이다. book 이 사라지면 quote 도 쓸 수 없다. split('.') 은 마침표로 문자열을 나누고, .next() 가 첫 조각을 Option 으로 준다(26편).
지금까지 수명을 안 적고도 참조를 잘 써 온 것은, 매개변수가 하나뿐이거나 메서드의 &self 처럼 답이 뻔한 경우를 컴파일러가 알아서 채웠기 때문이다. 큰따옴표 문자열의 타입은 정확히 &'static str 인데, 'static 은 "프로그램이 끝날 때까지 산다" 는 뜻이다. 다음 편은 이름 없는 작은 함수, 클로저다.