ライフタイム入門
参照が有効な期間を示す「ライフタイム」について学びます。
ライフタイムとは
ライフタイムは「参照が有効な期間」を表します。
fn main() {
let r; // ---------+-- 'a
// |
{ // |
let x = 5; // -+-- 'b |
r = &x; // | |
} // -+ | (xが破棄される)
// |
// println!("{}", r); // エラー! | (rは無効なメモリを指す)
} // ---------+
xのライフタイム'bはrのライフタイム'aより短いため、エラーになります。
なぜライフタイムが必要なのか
ダングリング参照を防ぐ
#![allow(unused)]
fn main() {
// これはコンパイルエラー
fn dangle() -> &String {
let s = String::from("hello");
&s // sへの参照を返そうとしている
} // ← sがドロップされる!参照先がなくなる
}
ライフタイムによって、コンパイラは「参照が参照先より長生きしないか」をチェックします。
関数のライフタイム注釈
問題のあるコード
#![allow(unused)]
fn main() {
// これはコンパイルエラー
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}
}
エラー:
error[E0106]: missing lifetime specifier
コンパイラは「返される参照がxのライフタイムなのかyのライフタイムなのかわからない」と言っています。
ライフタイム注釈で解決
#![allow(unused)]
fn main() {
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
}
<'a>: ライフタイムパラメータの宣言&'a str: ライフタイム'aを持つ文字列参照
この注釈は「返される参照は、xとyの両方が有効な間だけ有効」という意味です。
ライフタイム注釈の構文
#![allow(unused)]
fn main() {
&i32 // 参照
&'a i32 // ライフタイム'aを持つ参照
&'a mut i32 // ライフタイム'aを持つ可変参照
}
具体例で理解する
fn main() {
let string1 = String::from("long string is long");
{
let string2 = String::from("xyz");
let result = longest(&string1, &string2);
println!("最長: {}", result); // OK: string2はまだ有効
}
// ここでresultを使おうとすると...?
}
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
これはOKです。resultは内側のスコープ内でのみ使われており、その時点ではstring1もstring2も有効だからです。
エラーになるケース
fn main() {
let string1 = String::from("long string is long");
let result;
{
let string2 = String::from("xyz");
result = longest(&string1, &string2);
} // string2がドロップされる
println!("最長: {}", result); // エラー!string2はもうない
}
resultはstring2を参照する可能性があるのに、string2はスコープを抜けて無効になっています。
ライフタイム省略規則
すべてのケースでライフタイムを書く必要はありません。
省略できるケース
#![allow(unused)]
fn main() {
// 明示的なライフタイム
fn first_word<'a>(s: &'a str) -> &'a str { ... }
// 省略可能(コンパイラが推論)
fn first_word(s: &str) -> &str { ... }
}
省略規則(参考)
- 各引数の参照は別々のライフタイムを得る
- 入力ライフタイムが1つなら、それが出力に適用される
&selfや&mut selfがある場合、selfのライフタイムが出力に適用される
最初は「省略できることがある」程度の理解で大丈夫です。
構造体のライフタイム
参照を持つ構造体にはライフタイム注釈が必要です。
struct ImportantExcerpt<'a> {
part: &'a str,
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().unwrap();
let excerpt = ImportantExcerpt {
part: first_sentence,
};
println!("{}", excerpt.part);
}
この構造体は「partが参照しているデータが有効な間だけ有効」です。
’static ライフタイム
'staticは「プログラム全体で有効」な特別なライフタイムです。
#![allow(unused)]
fn main() {
let s: &'static str = "Hello, world!";
}
文字列リテラルは'staticライフタイムを持ちます。プログラムに埋め込まれているため、常に有効です。
注意
'staticを安易に使うのは避けましょう。本当に必要なケースは稀です。
ライフタイムのメンタルモデル
┌────────────────────────────────────────────────────────┐
│ ライフタイム = 「参照が有効な期間の制約」 │
├────────────────────────────────────────────────────────┤
│ │
│ 目的: ダングリング参照を防ぐ │
│ │
│ ルール: │
│ ・参照は参照先より長生きしてはいけない │
│ ・関数が複数の参照を受け取り参照を返す場合、 │
│ ライフタイム注釈で関係を明示 │
│ │
│ 省略: 多くの場合、コンパイラが推論してくれる │
│ │
└────────────────────────────────────────────────────────┘
まとめ
| 概念 | 説明 |
|---|---|
| ライフタイム | 参照が有効な期間 |
'a | ライフタイムパラメータ |
| 注釈の目的 | 参照間の関係を明示 |
'static | プログラム全体で有効 |
最初は難しく感じますが、使っていくうちに自然と理解できるようになります。
確認テスト
Q1. ライフタイムの主な目的は何ですか?
Q2. 'staticライフタイムが意味するものは何ですか?
'staticはプログラムの開始から終了まで有効な最も長いライフタイムです。文字列リテラルは'staticライフタイムを持ちます。
Q3. 以下のコードはコンパイルできますか?let r; { let x = 5; r = &x; } println!("{}", r);
xは内側のスコープを抜けると破棄されますが、rは外側のスコープでxへの参照を使おうとしています。xのライフタイムがrより短いため、ダングリング参照になります。
Q4. 関数fn longest(x: &str, y: &str) -> &strがコンパイルエラーになる理由は何ですか?
xのライフタイムなのかyのライフタイムなのかわかりません。fn longest<'a>(x: &'a str, y: &'a str) -> &'a strのようにライフタイムパラメータを追加する必要があります。
Q5. 構造体に参照を持たせる場合、必要なものは何ですか?
struct Excerpt<'a> { text: &'a str }のようにライフタイムパラメータが必要です。これは「この構造体は参照先が有効な間だけ有効」ということを意味します。
次のドキュメント: 05_slices.md