メモ > 技術 > プログラミング言語: Rust > ライフタイム
ライフタイム
&を用いた参照を利用した際に、解放されたメモリを参照してしまうことを避けるための仕組み
例えば以下のプログラムは、実行すると「error[E0597]: `x` does not live long enough」のエラーになる
printlnでrを参照する時点で、参照元のxは破棄されて内容を参照できないため
fn main() {
{
let r;
{
let x = 5;
r = &x;
}
println!("r: {}", r);
}
}
上記はプログラムが単純だが、「&str」で文字列を参照する際に同じ問題が発生する
RustのLifetimeってなんなん
https://zenn.dev/ucwork/articles/6de5c9c2257f2d
Rustのライフタイムについて知りたい #Rust - Qiita
https://qiita.com/toreis/items/970bcfed6a930e3c84dc
Rust の 所有権、借用、ライフタイムについて初心者目線で説明と整理を試みる | blog.ojisan.io
https://blog.ojisan.io/rust-ownership-wakaran/
■関数でのライフタイム
以下は文字列の長さを比較するプログラムだが、実行すると「error[E0106]: missing lifetime specifier」のエラーになる
関数longestにxとyが渡されているが、関数からどちらの値が返ってくるかコンパイル時に確定できないため
fn main() {
let string1 = "ABC";
let string2 = "ABCD";
let result = longest(string1, string2);
println!("The longest string is: {}", result);
}
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}
以下のとおりライフタイム「'a」を設定すると、xとyの両方が同じ期間だけ生きることを宣言でき、エラーにならなくなる
(実行結果は「The longest string is: ABCD」となる)
fn main() {
let string1 = "ABC";
let string2 = "ABCD";
let result = longest(string1, string2);
println!("The longest string is: {}", result);
}
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
また以下のように、「参照を含まずにデータを共有する」ように変更することでもエラーを回避できる
(前述の「借用と参照」にあるコードと同じような内容になる)
ライフタイムの管理が不要になるメリットはあるが、文字列のコピーが発生するのでメモリ使用量が増え、パフォーマンスも落ちる
fn main() {
let string1 = String::from("ABC");
let string2 = String::from("ABCD");
let result = longest(string1, string2);
println!("The longest string is: {}", result);
}
fn longest(x: String, y: String) -> String {
if x.len() > y.len() {
x
} else {
y
}
}
■構造体でのライフタイム
以下は構造体を使用するプログラムだが、実行すると「error[E0106]: missing lifetime specifier」のエラーになる
struct Person {
name: &str,
}
fn main() {
let taro = Person {
name: "山田太郎",
};
println!("{}さんこんにちは。", taro.name);
}
以下のとおりライフタイム「'a」を設定すると、xとyの両方が同じ期間だけ生きることを宣言でき、エラーにならなくなる
(実行結果は「山田太郎さんこんにちは。」となる)
struct Person<'a> {
name: &'a str,
}
fn main() {
let taro = Person {
name: "山田太郎",
};
println!("{}さんこんにちは。", taro.name);
}
また以下のように、「参照を含まずにデータを共有する」ように変更することでもエラーを回避できる
(前述の「構造体」にあるコードと同じような内容になる)
ライフタイムの管理が不要になるメリットはあるが、文字列のコピーが発生するのでメモリ使用量が増え、パフォーマンスも落ちる
struct Person {
name: String,
}
fn main() {
let taro = Person {
name: String::from("山田太郎"),
};
println!("{}さんこんにちは。", taro.name);
}
■その他
初期はライフタイムの明記が必須だったらしいが、今は以下の場合にライフタイムを明記しなくてもいいようになっている…らしい
構造体のメソッドはselfを持つことが多いので、ライフタイムの指定は不要になることが多い…らしい
・参照の各引数は、独自のライフタイム引数を得る
・1つだけ入力ライフタイム引数があるなら、そのライフタイムが全ての出力ライフタイム引数に代入される
・selfのあるメソッドの場合、selfのライフタイムが全出力ライフタイム引数に代入される
以下は構造体のメソッドfirst_charを定義しているが、このように「self」が渡されることが多い
struct Person<'a> {
name: &'a str,
}
impl<'a> Person<'a> {
fn first_char(&self) -> Option<char> {
self.name.chars().next()
}
}
fn main() {
let taro = Person {
name: "山田太郎",
};
println!("{}さんこんにちは。", taro.name);
if let Some(first) = taro.first_char() {
println!("先頭の文字は{}です。", first);
}
}
以下メモ
・「'a」という記述があるが、これはライフタイム指定でジェネリクスの一種
ライフタイムは、Rustの借用チェッカーが参照の有効期間を検証するための仕組み
「a」という名前は、「src」など任意のものを付けられる
例えば「Value<'src>」の場合、列挙型「Value」内で参照を持つフィールドのライフタイムが「src」であることを示している
・ライフタイムは、コンパイラが参照の有効期間を追跡し、メモリ安全性を保障する
複数の参照が同じライフタイムを共有するか、または別々のライフタイムを持つかを明示することで、より厳密なメモリ管理を行なう
・コンパイラは多くの場合、自動的にライフタイムを推測することができるが、特に複雑なケースでは明示的なライフタイム指定が必要になる
また構造体や列挙型のような型定義で参照を含む場合、ライフタイム指定が必須になる
・ライフタイムは他の言語に似た概念が存在しない