RustのLifetimeってなんなん
概要
Rustチュートリアル読んでてOwnershipの次に「むむ」ってなる
Lifetimeにたどり着いたので頭の中を整理してみる
Lifetimeってなんなん?
&を用いた参照(Reference)を利用した際に
Dangling Referenceが発生しないように利用する概念みたい
(Dangling Reference:解放されたメモリを参照してしまう状態)
順を追ってチュートリアルを噛み砕いてみる
Dangling Referenceになるコード
たとえばこれ
もう解放されたxの値(メモリ)を参照しようとしてDangling Reference状態
fn main() {
let r;
{
let x = 5;
r = &x;
} // xがdropされる
println!("r is {}", r); // dropされたxを参照してるr君
}
どうやってDangling Referenceを判定してるの?
RustのコンパイラにはBorrow Checkerなる機能があるらしく
こいつがLifetimeっていう概念を用いて、Dangling Referenceを判定してるみたい
fn main() {
let r; // ---------+-- 'a
// |
{ // |
let x = 5; // -+-- 'b |
r = &x; // | |
} // -+ |
// |
println!("r: {}", r); // |
} // ---------+
上記コメントに記載しましたが
変数rは'a
で示した期間(Lifetime)有効である。長いね
変数xは'b
で示した期間(Lifetime)有効である。短いね
コンパイル時に、
「rがxを参照してるぞ。でも参照してる'b
のLifetimeは自分の'a
Lifetimeより短いやんけ!エラー!」
とそれぞれの長さを見て判定してるらしい。
このBorrow CheckerによるLifetime判定の影響で
Rustでは自分で明示的にLifetimeをannotationしてあげないとコンパイルエラーになるケースがあるみたい。
Lifetimeのannotationを付与する
関数
コンパイルエラーになっちゃうケース
この関数はまさにコンパイルエラーが発生しちゃう
fn main() {
let string1 = String::from("abcd");
let string2 = "xyz";
let result = longest(string1.as_str(), string2);
println!("The longest string is {}", result);
}
// 戻り値の参照がどちらの引数の参照をしてるか、コンパイル時に判断不能
// なので、Borrow CheckerがDangling Referenceのチェックができないのよ・・・
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}
解決方法
引数と戻り値にLifetime Annotationをつけてあげる
ジェネリクスの記法に準じて、関数に<'a>
を渡して
引数と戻り値にそれぞれ&'a
こんな感じでannotationをつけてあげるとコンパイルエラーから抜けることができる!
fn main() {
let string1 = String::from("abcd");
let string2 = "xyz";
let result = longest(string1.as_str(), string2);
println!("The longest string is {}", result);
}
// Lifetime Annotationつけてあげる
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
明示的に記述したLifetime'a
は
x, yの引数のうちLifetimeが短い方のLifetimeと等しい扱いになるみたいです
短い方のLifetimeってどう言うこと?
これがコンパイルエラーになっちゃう感じ
コメントした通り、短い方のlifetimeを見ちゃって
Borrow CheckerがDangling Referneceの可能性を考慮してコンパイルエラーにするのだ
fn main() {
let string1 = String::from("long string is long");
let result;
{
let string2 = String::from("xyz");
// resultはstring1なので
result = longest(string1.as_str(), string2.as_str());
}
// スコープ抜けたここでも有効なはずだけど
println!("The longest string is {}", result);
}
// `aのlifetimeは短い方のstring2を用いて、Borrow Checkerがチェックしてる
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
struct
コンパイルエラーになっちゃうケース
構造体のフィールドに参照がある場合、
lifetime annotationが指定されてないとコンパイルエラーになるらしい
#[derive(Debug)]
struct ImportantExcerpt {
part: &str,
}
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 };
println!("ImportantExcerpt is {:?}", i)
}
解決方法
lifetime annotationをつけてあげるのさ!
#[derive(Debug)]
struct ImportantExcerpt<'a> {
part: &'a str,
}
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 };
println!("ImportantExcerpt is {:?}", i)
}
Lifetimeのannotationが省略できるケース
全ての参照にLifetimeがあるが、明記しなくてもいいケースがあるらしい
昔(Rust1.0以前)は全部明記しなきゃいけなかったらしいけど
明らかにLifetimeの予測が可能な場合は省略できる様になったらしい
具体的には以下3つの規則に準じて、
Borrow Checkerが戻り値のLifetime判別できる場合省略可能
- 参照の各引数は、独自のライフタイム引数を得る
- 1つだけ入力ライフタイム引数があるなら、そのライフタイムが全ての出力ライフタイム引数に代入される
- selfのあるメソッドの場合、selfのライフタイムが全出力ライフタイム引数に代入される
省略できる例(メソッド)
fn first_word(s: &str) -> &str {
規則1に準じて引数に独自lifetime annotationを付与
fn first_word<'a>(s: &'a str) -> &str {
規則2に準じて引数ひとつなので、戻り値に引数のlifetimeが指定される
fn first_word<'a>(s: &'a str) -> &'a str {
戻り値のlifetime判明してるので省略可能じゃ!
省略できない例(関数)
fn longest(x: &str, y: &str) -> &str {
規則1に準じて各引数に独自lifetime annotationを付与
fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str {
規則2も3も適用できない・・・
戻り値のlifetime判明しない・・・ので明示的に設定が必要
省略できる例(メソッド)
メソッドにはこんな感じでジェネリクス形式のlifetime annotation指定できるらしい
impl<'a> ImportantExcerpt<'a> {
// 規則1に準じて戻り値のlifetimeわかるからannotationいらず
fn level(&self) -> i32 {
3
}
}
&self
がいるので3番目の規則に準じて
&selfのlifetimeが戻り値のlifetimeに適用される。annotationいらず!
impl<'a> ImportantExcerpt<'a> {
fn announce_and_return_part(&self, announcement: &str) -> &str {
println!("Attention please: {}", announcement);
self.part
}
}
structのメソッドはselfあることが多そうだし、結構省略できるんだなきっと🧐
静的lifetime
&'static
こんなlifetime指定を静的lifetimeていうらしい。
プログラムの全期間を指すlifetimeらしい
let s: &'static str = "I have a static lifetime.";
staticを指定しなさい!みたいなメッセージが出る時あるらしいけど
スコープは基本必要な時だけ、短い方が適切なケースがほとんどだろうし
むやみやたらにstaticつけるのはやめてちゃんと考えましょうね。ってことらしい
まとめ
チュートリアルのコピペみたいな内容になってしまった🙈
要は参照渡して参照返すとき、コンパイラがlifetimeわからなくなるケースがあるから
明示的に書いてあげるべきケースがあるってことみたい。
コンパイラに怒られたら、追記してあげるくらいの気持ちでいよう。
Discussion