Rustのライフタイム:参照の有効性への注釈
ライフタイムとは、Rustが「参照が決して滞留しない」ことを保証するために用いる仕組みであり、各参照に「有効期限」を割り当てるものです。
実は、あなたはすでにライフタイムを使用しています。レッスン9と10のコードにはすべてライフタイムが含まれていますが、コンパイラが自動的に「推測」していただけです。このレッスンでは、いつ手動でライフタイムを指定する必要があるかについて学びます。
1. 学習内容
- ライフサイクルが必要な理由 — ダングリング参照を防ぐため
- ライフサイクル注釈の構文:
'a、'b - 関数シグネチャにおけるライフタイム――パラメータと戻り値のライフタイム
- ライフタイムの省略規則――コンパイラはいつ自動的に推論できるのか?
- 構造体におけるライフサイクル注釈
'staticライフサイクル—永続参照
2. 概念図
flowchart LR
subgraph "Function Signature fn longest<`'a`>"
X["x: &'a str"] --> RET["Return Value: &'a str"]
Y["y: &'a str"] --> RET
end
NOTE["'a = the shorter of x and y's lifetimes"] -.-> RET
3. ネズミを追いかけた話
(1) もどかしさ:ネズミは逃げたけれど、私はまだ見ている
トムは生物学者で、トラッカーを使ってネズミの位置を記録しています:
The tracker locks onto the mouse A → Mouse A ran into the cave → The tracker is still pointing toward the entrance.
- トラッカー(参照)はマウスA(データ)を指しています
- マウスAが逃げ出した(データが失われた)
- トレーサーは依然としてその場所を指しています(ダングリング参照――危険です!)
「トラッカーに『マウスAが穴から出てくるまで有効』というラベルが付いていたら、最高なんだけど……」
(2) Rustのライフサイクルに関する解決策
// Life Cycle Annotation 'a: Tell the compiler "the validity period of the return value does not exceed that of parameter x's validity period"
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("Long Strings");
let result;
{
let string2 = String::from("short");
result = longest(&string1, &string2); // ✅ string2 and string1 are both still alive
println!("The longer one is: {}", result);
} // string2 Destroyed here
// println!("{}", result); // ❌ If you print here, result references string2 which is destroyed
} // string1 Destroyed here
ライフタイムは、トラッカーに付けられた「有効期限」ラベルのようなものです。コンパイラは、これらのラベルを用いて、参照がまだそのライフタイムの範囲内にあるかどうかを判断します。この例では、
resultのライフタイムはstring2のライフタイムを超えることはできません。
4. ライフサイクルの仕組み
(1) すべての参照にはライフサイクルがある
fn main() {
let x: i32 = 10; // x Its life cycle begins here
let r: &i32 = &x; // r Its life cycle begins here
println!("r: {}", r); // Usage r
} // r whose lifecycle ends first, then x ends
(2) ライフサイクルに注釈を付ける必要があるのはなぜですか?
関数が参照を返す場合、コンパイラはその参照がどの引数から来ているのかを知る必要があります:
graph TB
A[Functions Return References] --> B{Which parameter does the return value point to?}
B --> C[Pointer Parameters1 → The Lifecycle of Return Values ≤ Parameters1]
B --> D[Pointer Parameters2 → The Lifecycle of Return Values ≤ Parameters2]
B --> E[Point to one of the two → Must be labeled: Find the intersection]
C --> F[No annotation required (the compiler can infer)]
D --> F
E --> G[Manual annotation is required 'a]
| シナリオ | 例 | アノテーションは必要か? |
|---|---|---|
| 単一入力基準 | fn first(x: &str) -> &str |
不要(省略規則) |
| 複数の入力参照 | fn longest(x: &str, y: &str) -> &str |
必須(コンパイラはどれを選択すべきか分からない) |
| 参照を返さない | fn len(x: &str) -> usize |
不要 |
| 参照を含む構造体 | struct S<'a> { r: &'a i32 } |
必須 |
(3) ライフサイクル注釈の位置に関するクイックリファレンス
| 場所 | 構文 | 説明 |
|---|---|---|
| 関数の定義 | fn foo<'a>(x: &'a str) -> &'a str |
引数と戻り値 |
| 構造体の定義 | struct S<'a> { r: &'a str } |
構造体は、その参照が破棄された後も存続することはできない |
| 実装ブロック | impl<'a> S<'a> |
メソッドの実装時のライフサイクルの宣言 |
| トレイトの制約 | where T: 'a |
制約型には短命な参照を含めることはできない |
| 静的アノテーション | &'static str |
プログラムの実行全体を通じて有効 |
5. ライフサイクル注釈
(1) 構文
// 'a is the name of the lifecycle parameter (using 'a, 'b, 'c)
// Pronunciation: Regarding the lifecycle 'a, both x and y need to be alive for at least 'a
fn function<'a>(x: &'a str, y: &'a str) -> &'a str {
// The lifecycle of return values = the shorter of x and y
}
(2) ライフサイクル除外ルール
コンパイラは、以下の3つのケースにおいて、ライフタイムを(ユーザーが明示的に記述することなく)自動的に推論することができます:
| ルール | 意味 |
|---|---|
| 各入力参照には独自のライフサイクルがあります | fn foo(x: &str) は x に自動的にライフサイクルを割り当てます |
| 入力参照は1つだけ | 出力参照の存続期間 = 入力参照の存続期間 |
| 複数の入力があり、そのうちの1つが &self または &mut self である | 出力参照のライフタイムは &self である |
// No annotation required: there is only one input reference
fn first_word(s: &str) -> &str { &s[..] }
// Needs annotation: two input references
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
6. ライフサイクルの例
(1) ▶ サンプル:ライフサイクルはなぜ必要なのか?(難易度 ⭐⭐⭐)
// ============================================
// What happens if there are no lifecycle annotations?
// ============================================
// This function has two input references, the return value could be any one of these
// ❌ If not annotated, a compilation error will occur: expected lifetime parameter
// fn longest_wrong(x: &str, y: &str) -> &str {
// if x.len() > y.len() { x } else { y }
// }
// ✅ Correct Version: Lifecycle Annotation
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
fn main() {
let s1 = String::from("hello");
let result;
{
let s2 = String::from("hi");
result = longest(&s1, &s2); // result lifecycle = s2 lifecycle (the shorter one)
println!("The longer one is: {}", result);
} // s2 destroyed, result invalid
// println!("{}", result); // ❌ The compiler will prevent: result's lifecycle has ended
}
出力:
The longer one is: hello
有効期間
'aは、xとyのうち短い方に設定されます。もし戻り値の有効期間を長い方に設定した場合、短い方が破棄された後も、その戻り値がまだ使用中のままになる可能性があります。これはまさに、コンパイラが防ごうとしている事態です。
(2) ▶ サンプル:さまざまなライフサイクルにおけるパラメータ(難易度 ⭐⭐⭐)
// ============================================
// The two parameters have different lifecycles.
// ============================================
// Two Lifecycles: 'a corresponds to x, 'b corresponds to y
// The return value depends only on x, so use 'a
fn choose_first<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
x // Return only x, so the lifecycle only needs 'a
}
fn main() {
let x = String::from("Live to be a hundred years old");
let result;
{
let y = String::from("Short-lived ghost");
result = choose_first(&x, &y); // ✅ result Depends solely on x
println!("result: {}", result);
} // y destroyed, but result does not depend on y, so no problem
println!("Remains valid even after leaving the inner layer: {}", result); // ✅ x Still Alive
}
出力:
result: Live to be a hundred years old
Remains valid even after leaving the inner layer: Live to be a hundred years old
戻り値が特定のパラメータのみに依存している場合、そのパラメータのライフタイムにのみアノテーションを付けるだけで十分です。
y内のchoose_firstパラメータのライフタイムが短くても、結果には影響しません。なぜなら、戻り値はyのデータを一切使用していないからです。
(3) ▶ サンプル:構造体におけるライフサイクル(難易度 ⭐⭐⭐⭐)
// ============================================
// Storing references in a structure -- the lifecycle must be specified.
// ============================================
struct Excerpt<'a> {
content: &'a str, // Structures Borrow External Strings
}
impl<'a> Excerpt<'a> {
fn length(&self) -> usize {
self.content.len()
}
fn announce(&self, announcement: &str) -> &str {
println!("Announcement: {}", announcement);
self.content // Returning a Reference to the Interior of a Structure
}
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().expect("Can't find the period");
let excerpt = Excerpt {
content: &first_sentence,
};
println!("Excerpt: {}", excerpt.content);
println!("Length: {}", excerpt.length());
println!("Announcement of Results: {}", excerpt.announce("Welcome to this article"));
} // excerpt must be destroyed before novel (content depends on novel)
出力:
Excerpt: Call me Ishmael
Length: 15
Announcement of Results: Call me Ishmael
構造体に参照が含まれる場合、構造体名にライフタイムパラメータ
<'a>を指定する必要があります。これにより、構造体インスタンスのライフタイムは、それが参照するデータのライフタイムを超えることはできないことがコンパイラに通知されます。
(4) ▶ サンプル:「static」のライフサイクル (難易度 ⭐⭐⭐)
// ============================================
// 'static: the reference remains valid throughout the program's execution.
// ============================================
fn main() {
// A string literal is 'static
let s: &'static str = "I exist throughout the entire lifecycle of a program";
println!("{}", s);
// 'static Constraints: T must not contain any non-static references
fn print_static<T: 'static>(val: &T) {
println!("{:?}", std::ptr::from_ref(val));
}
let num: i32 = 42;
print_static(&num); // ✅ i32 excludes references, complies with 'static
// let msg = String::from("temp");
// let r = &msg;
// print_static(&r); // ❌ r references non-'static msg
}
出力:
I exist throughout the entire lifecycle of a program
0x...(Memory Address)
'staticは Rust において最も長いライフタイムであり、プログラムの開始時から終了時までデータが存在し続けます。文字列リテラルは'staticのライフタイムを持ちます。注意:'staticは、「このデータが終了時まで存続する」という意味ではなく、「この参照は永久に有効である」という意味として解釈されるべきです。
(5) ▶ サンプル:総合演習—テキストアナライザー(難易度 ⭐⭐⭐)
// ============================================
// Life Cycle in Practice: Zero-Copy Text Analysis
// ============================================
struct TextAnalyzer<'a> {
text: &'a str,
}
impl<'a> TextAnalyzer<'a> {
fn new(text: &'a str) -> Self {
TextAnalyzer { text }
}
fn word_count(&self) -> usize {
self.text.split_whitespace().count()
}
fn longest_word(&self) -> &'a str {
self.text
.split_whitespace()
.max_by_key(|w| w.len())
.unwrap_or("")
}
fn first_n_words(&self, n: usize) -> Vec<&'a str> {
self.text.split_whitespace().take(n).collect()
}
fn line_count(&self) -> usize {
self.text.lines().count()
}
fn char_count(&self) -> usize {
self.text.chars().count()
}
fn summary(&self) -> String {
format!(
"Character: {}, Words: {}, Number of lines: {}, Longest Word: '{}'",
self.char_count(),
self.word_count(),
self.line_count(),
self.longest_word()
)
}
}
fn highlight_word<'a>(text: &'a str, word: &str) -> Vec<&'a str> {
text.split_whitespace()
.filter(|w| w.contains(word))
.collect()
}
fn main() {
let article = "Rust is a systems programming language \
that runs blazingly fast and prevents segfaults. \
Rust guarantees memory safety and thread safety.";
let analyzer = TextAnalyzer::new(article);
println!("=== Text Analysis ===");
println!("{}", analyzer.summary());
let top3 = analyzer.first_n_words(3);
println!("\nFirst 3 words: {:?}", top3);
let rust_mentions = highlight_word(article, "Rust");
println!("Includes 'Rust' the word: {:?}", rust_mentions);
let paragraph = "First line.\nSecond line.\nThird line.";
let p_analyzer = TextAnalyzer::new(paragraph);
println!("\nParagraph Analysis: {}", p_analyzer.summary());
}
出力:
=== Text Analysis ===
Character: 124, Words: 18, Number of lines: 1, Longest Word: 'blazingly'
First 3 words: ["Rust", "is", "a"]
Includes 'Rust' the word: ["Rust", "Rust"]
Paragraph Analysis: Character: 39, Words: 6, Number of lines: 3, Longest Word: 'Second'
TextAnalyzerライフサイクル全体を通じて原文への参照を保持します<'a>—すべての解析手法はゼロコピーです。longest_word返される&'a strは原文の単語スライスを指しており、メモリの割り当ては一切行われません。
❓ よくある質問
📖 まとめ
- ライフタイムとは、参照が有効な状態を維持されることを保証するためにRustが用いるコンパイル時の仕組みです。
'aという構文を使用して、入力参照と出力参照の有効期間をリンクします- ライフサイクル除外ルールにより、80%以上のシナリオにおいて手動での注釈付けが不要になります
- 構造体に参照を格納する場合、ライフタイムパラメータを指定する必要があります。
'staticはライフサイクル(プログラムの実行期間全体)が最も長い- ライフサイクル全体を通じて実行時のオーバーヘッドが発生しない。コンパイルされると、その存在は消える。
📝 練習問題
- 難易度 ⭐:既存のRustコード(第9課の例など)を読み、ライフタイム省略ルールのおかげで手動でのアノテーションが不要になっている関数を特定してください。
- 難易度 ⭐⭐⭐: 2つの文字列のうち短い方を返す関数
fn shortest<'a>(x: &'a str, y: &'a str) -> &'a strを作成してください。mainでは、この関数を呼び出すために、有効期間が異なる変数を作成してください。 - 難易度 ⭐⭐⭐⭐: 型
&'a strのフィールドtitleを含む構造体Book<'a>を定義してください。タイトルの最初の単語を返すメソッドfn first_word(&self) -> &strを実装するimplブロックを作成してください。その使用例をmainで示してください。



