아래 코드는 에러가 발생한다. picked_value의 라이프타임이 int2의 라이프타임과 같은데(두 개의 파라미터 중 라이프타임이 짧은 쪽을 반환 값의 라이프타임으로 반환하기 때문), int2의 유효 라이프타임 범위 밖에서 picked_value를 출력하려 했기 때문이다.
fn main() {
let int1 = 5;
let picked_value;
{
let int2 = 10;
picked_value = picking_int(&int1, &int2);
}
println!("{picked_value}");
}
fn picking_int<'a>(i: &'a i32, j: &'a i32) -> &'a i32 { // 둘 중 더 짧은 라이프타임을 반환 값에 적용한다 (만약 i가 j보다 더 짦은 라이프타임을 갖는다면 반환 값의 라이프타임은 i와 동일하다.)
if rand::random() {
i
} else {
j
}
}
Standard Error
Compiling playground v0.0.1 (/playground)
error[E0597]: `int2` does not live long enough
--> src/main.rs:6:43
|
5 | let int2 = 10;
| ---- binding `int2` declared here
6 | picked_value = picking_int(&int1, &int2);
| ^^^^^ borrowed value does not live long enough
7 | }
| - `int2` dropped here while still borrowed
8 | println!("{picked_value}");
| -------------- borrow later used here
For more information about this error, try `rustc --explain E0597`.
error: could not compile `playground` (bin "playground") due to 1 previous error
Standard Output
위의 에러를 수정하려면 main함수 안에 있는 {}를 삭제하거나, 혹은 다음과 같이 picking_int 함수의 반환 값이 i가 되도록 작성할 수 있다.
fn main() {
let int1 = 5;
let picked_value;
{
let int2 = 10;
picked_value = picking_int(&int1, &int2);
}
println!("{picked_value}");
}
fn picking_int<'a>(i: &'a i32, j: &i32) -> &'a i32 {
i
}
만약 특정 값을 함수 안에서 정의하여, 해당 값의 참조를 반환하고 싶으면 어떻게 할까? 아래와 같이 코드를 작성하면 에러가 발생한다.
fn picking_int<'a>(i: &'a i32, j: &'a i32) -> &'a i32 {
let y = 6; // `y`는 스코프를 벗어나면 사라짐
&y // ERROR: y는 'a 라이프타임을 만족하지 않음
}
error[E0515]: cannot return reference to local variable `y`
--> src/main.rs:13:5
|
13 | &y // ERROR: y는 'a 라이프타임을 만족하지 않음
| ^^ returns a reference to data owned by the current function
For more information about this error, try `rustc --explain E0515`.
warning: `playground` (bin "playground") generated 2 warnings
error: could not compile `playground` (bin "playground") due to 1 previous error; 2 warnings emitted
함수 내에서 선언한 y의 라이프타임이 함수를 벗어나는 순간 유효하지 않기 때문에 컴파일러는 에러를 발생시키고 있다.
대신 함수 내애서 정의한 값의 참조를 반환하기 위한 방법으로 static 라이프타임을 사용할 수 있다. static 라이프타임은 프로그램 전체에서 유효한 라이프타임이다.
fn picking_int<'a>(i: &'a i32, j: &'a i32) -> &'a i32 {
let y: &'static i32 = &6;
y
}
위와 같이 코드를 작성하고 실행하면 문제없이 컴파일 되지만, 실제 반환 값이 라이프타임a를 반환하지 않았는데, 반환 라이프타임이 a가 되도록 명시하는것이 이상하다. 근데 왜 에러가 아닐까?
Rust는 반환값의 실제 라이프타임인 'static이 입력 라이프타임 'a를 포괄적으로 포함(outlive)하므로, 반환 라이프타임 'a에도 문제가 없다고 판단하기 때문이다. Rust 라이프타임 시스템은 더 긴 라이프타임이 더 짧은 라이프타임을 자동으로 만족시킨다는 원칙을 따른다. 즉, 'static은 'a 라이프타임보다 더 긴 기간 동안 유효하기 때문에, 'static 참조를 'a 라이프타임으로 안전하게 변환(캐스팅)할 수 있다.
하지만 코드의 의도를 명확히 전달하기 위해 함수는 다음과 같이 수정하자.
fn picking_int(i: &i32, j: &i32) -> &'static i32 {
let y: &'static i32 = &6;
y
}
fn main() {
let mut some_str = String::from("I am String");
let ref1 = &some_str; // 불변 참조 생성
println!("{ref1}"); // move this line only
let ref2 = &mut some_str; // 가변 참조 생성
ref2.push_str(" additional information");
println!("{ref2}");
}
원칙대로라면, 위의 코드는 아래의 빌림 규칙 1번에 위배되므로 에러가 발생해야한다.
참조가 생성되고 사용되는 스코프는 빌림 체크의 핵심이다. 참조가 더 이상 사용되지 않는 시점에는 빌림이 해제된다(이는 컴파일러가 자동으로 판단).
하지만 위의 예제 코드는 문제없이 컴파일되었다. 왜?
-> 참조(ref1)가 더 이상 사용되지 않는다고 컴파일러가 판단했기 때문.
Rust는 참조가 더 이상 사용되지 않는 경우, 해당 참조의 스코프를 조기에 종료시킨다(이를 Non-Lexical Lifetimes(NLL)라 한다).
만약 코드를 아래와 같이 수정한다면, 에러가 발생한다.
fn main() {
let mut some_str = String::from("I am String");
let ref1 = &some_str;
let ref2 = &mut some_str;
ref2.push_str(" additional information");
println!("{ref1}"); // move this line only
println!("{ref2}");
}
위의 코드에서는 ref1을 ref2이후에 다시 사용하려했다(println()). 이 렇게 되면 ref1의 스코프가 종료되지 않은 상태에서 ref2를 some_str에 대한 가변참조로 정의했기 때문에 명백히 빌림 규칙1에 위배된다.
rust에서는 라이프타임 생략 규칙 덕분에 대부분의 경우 라이프타임 명시가 필요 없음.
각 참조 매개변수는 고유한 라이프타임을 갖기 때문에 굳이 명시하지 않아도 컴파일러가 내부적으로 알아서 변환한다.
fn foo(x: &i32, y: &i32);
// 컴파일러가 변환:
// fn foo<'a, 'b>(x: &'a i32, y: &'b i32);
fn pick_str(x: &str, y: &str) -> &str;
// 컴파일러가 변환:
// fn pick_str<'a, 'b>(x: &'a str, y: &'b str) -> &str ; ❌ 컴파일 에러 발생(반환 라이프타임이 뭔지 모름)
위와 같은 상황에서는 반환값의 라이프타임을 추론할 수 없기 때문에 아래와 같이 명시해줘야한다.
fn pick_str<'a>(x: &'a str, y: &'a str) -> &'a str;
fn get_str(s: &str) -> &str;
// 컴파일러가 변환:
// fn get_str<'a>(s: &'a str) -> &'a str;
struct Person {
name: String,
}
impl Person {
fn get_name(&self) -> &str {
&self.name
}
}
// 컴파일러가 변환:
// fn get_name<'a>(&'a self) -> &'a str {}
fn create_str() -> &str; // ❌ 컴파일 에러 발생!
이 함수에서는 반환값의 라이프타임을 추론할 수 없고, 반환값의 라이프타임이 어디에서 유래했는지 명확하지 않으므로 아래와 같이 명시해야함
fn create_str<'a>() -> &'a str { ... } // 특정 라이프타임 지정