Rust - 라이프타임(lifetime)은 또 뭡니까?
목차
라이프타임
파이썬 개발자에게 어쩌면 Rust는 취미로 공부하기엔 너무 빡센 언어인것 같기도 합니다. 라이프타임(lifetime)
까지 진도를 나왔는데 어려워서 블로그에 정리하며 공부해보려고 합니다.
아래 문서를 보면서 하고 있습니다.
라이프타임으로 참조자의 유효성 검증하기 - The Rust Programming Language
라이프타임은 메모리 안정성을 보장하기 위한 Rust의 여러 장치들 중 하나인 것 같습니다. 제네릭의 한 종류이며, 어떤 참조자가 얼마나 오랫동안 유효한지 컴파일러가 추적하고 검증하는 방식입니다.
Rust는 메모리 관리를 컴파일 타임에 진행하는데 가비지 컬렉터도 없는데 안전한 메모리 관리와 성능을 낼 수 있는 것은 이런 장치들 때문입니다.
모든 참조자는 라이프타임을 가진다
이 문장을 기억하고 진행합니다.
라이프타임의 주목적은 Dangling Reference 방지라고 합니다. 이제 다음과 같은 코드는 너무 쉽죠
fn main() {
let r;
{
let x = 5;
r = &x;
}
// r이 x를 참조하고 있는데 스코프를 벗어나서 메모리 해제 되었는데
// r을 어떻게 출력하냐?
println!("r: {}", r);
}
// 이게 옳게된 코드죠
fn main() {
let x = 5; // ----------+-- 'b
// |
let r = &x; // --+-- 'a |
// | |
println!("r: {}", r); // | |
// --+ |
} // ----------+
라이프 타임 사용 방법
위 코드에서 잠깐 보시다시피 ‘a
’b
이런 식으로 ‘
를 써서 사용합니다. 관습적으로 a
부터 시작하는 것 같습니다.
보통 함수나 struct를 정의할 때 사용합니다.
- 함수에서 사용
// 이게 왜 컴파일이 안될까?
// 정답은 x나 y나 어떤게 반환될 지 모르기 때문에
// 컴파일러가 매개변수들의 라이프타임을 알 수 없음
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}
// 따라서 컴파일러가 알 수 있도록 라이프타임을 명시적으로 지정해주어야 합니다.
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
// 이렇게 쓸 수 있겠습니다.
&'a i32
&'a mut str
&'a Vec<i32>
...
- struct에서 사용
- 이건 좀 이해 하느라 애를 먹었네요.
// part필드에 라이프타임이 명시되어야 하는 이유
// 입력받는 필드 part는 입력된 참조자 필드보다 오랫동안 살아 있을 수 없기 떄문입니다.
struct ImportantExcerpt<'a> {
part: &'a str,
}
// novel에서 나온 first_sentence를 참조로 받기 때문에
// novel보다 라이프타임이 길수 없다고 명시해주어야 하는 것입니다.
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().expect("Could not find a '.'");
let I = ImportantExcerpt {
part: first_sentence,
};
}
라이프타임 생략(lifetime elision)
라이프타임을 컴파일러가 추론할 수 있다면 생략이 가능합니다. 러스트 처음 공부할 때 예제에는 라이프타임 같은거 생략되어 있었어요.
컴파일러가 라이프타임이 명시되어 있지 않을 때 추론하는 규칙은 3가지라고 합니다.
- 규칙1: 각각 매개변수에 개별 라이프타임 대입
- 규칙2: 매개변수가 1개면 출력에도 같은 라이프타임 대입
- 규칙3: 매개변수가 여러 개인데 그중에
&self
,&mut self
가 있다면 출력에도self
와 같은 라이프타임 대입
위와 같은 이유 때문에 이전 예시에서 사용한 longest
는 라이프타임 추론이 불가 했던 것입니다.
// 입력 참조자가 하나라서 컴파일러가 라이프타임을 자동으로 추론 가능합니다.
fn get_string(s: &str) -> &str {
s
}
정적 라이프타임
static 라이프타임은 꽤 간단했는데요. 라이프타임이 프로그램 전체 생애 동안 살아있는 경우 사용합니다.
'static
이렇게 사용합니다.
let s: &'static str = "blah blah"
static을 사용하기 전엔 이게 꼭 필요한 것인지 한 번 더 고민하라고 합니다.
마무리
매서드에 사용하기, 제네릭 타입, 트레이트 바운드에 사용 등은 이 정도 정리해보니 알 것 같아서 생략했습니다.
현재 파이썬 백엔드 부트캠프에서 서브강사를 하고 있는데, 최근 훈련생분들 클래스 복습을 도와주면서 “개념적으론 알겠는데 코드를 써보려면 잘 모르겠다” 라는 이야기를 들었습니다. 제가 지금 Rust에서 이 현상을 겪고 있는지도 모르겠습니다. 그래도 화이팅.