404 Not Found

404 Not Found


nginx

Rustのライフタイム:参照の有効性への注釈

ライフタイムとは、Rustが「参照が決して滞留しない」ことを保証するために用いる仕組みであり、各参照に「有効期限」を割り当てるものです。

実は、あなたはすでにライフタイムを使用しています。レッスン9と10のコードにはすべてライフタイムが含まれていますが、コンパイラが自動的に「推測」していただけです。このレッスンでは、いつ手動でライフタイムを指定する必要があるかについて学びます。


1. 学習内容


2. 概念図

100%
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) もどかしさ:ネズミは逃げたけれど、私はまだ見ている

トムは生物学者で、トラッカーを使ってネズミの位置を記録しています:

TEXT
The tracker locks onto the mouse A → Mouse A ran into the cave → The tracker is still pointing toward the entrance.

「トラッカーに『マウスAが穴から出てくるまで有効』というラベルが付いていたら、最高なんだけど……」

(2) Rustのライフサイクルに関する解決策

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) すべての参照にはライフサイクルがある

RUST
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) ライフサイクルに注釈を付ける必要があるのはなぜですか?

関数が参照を返す場合、コンパイラはその参照がどの引数から来ているのかを知る必要があります:

100%
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) 構文

RUST
// '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 である
RUST
// 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) ▶ サンプル:ライフサイクルはなぜ必要なのか?(難易度 ⭐⭐⭐)

RUST
// ============================================
// 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
}

出力:

TEXT
The longer one is: hello

有効期間 'a は、xy のうち短い方に設定されます。もし戻り値の有効期間を長い方に設定した場合、短い方が破棄された後も、その戻り値がまだ使用中のままになる可能性があります。これはまさに、コンパイラが防ごうとしている事態です。


(2) ▶ サンプル:さまざまなライフサイクルにおけるパラメータ(難易度 ⭐⭐⭐)

RUST
// ============================================
// 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
}

出力:

TEXT
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) ▶ サンプル:構造体におけるライフサイクル(難易度 ⭐⭐⭐⭐)

RUST
// ============================================
// 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)

出力:

TEXT
Excerpt: Call me Ishmael
Length: 15
Announcement of Results: Call me Ishmael

構造体に参照が含まれる場合、構造体名にライフタイムパラメータ <'a> を指定する必要があります。これにより、構造体インスタンスのライフタイムは、それが参照するデータのライフタイムを超えることはできないことがコンパイラに通知されます。


(4) ▶ サンプル:「static」のライフサイクル (難易度 ⭐⭐⭐)

RUST
// ============================================
// '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
}

出力:

TEXT
I exist throughout the entire lifecycle of a program
0x...(Memory Address)

'static は Rust において最も長いライフタイムであり、プログラムの開始時から終了時までデータが存在し続けます。文字列リテラルは 'static のライフタイムを持ちます。注意:'static は、「このデータが終了時まで存続する」という意味ではなく、「この参照は永久に有効である」という意味として解釈されるべきです。


(5) ▶ サンプル:総合演習—テキストアナライザー(難易度 ⭐⭐⭐)

RUST
// ============================================
// 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
=== 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 は原文の単語スライスを指しており、メモリの割り当ては一切行われません。


❓ よくある質問

Q ライフタイムは実行時の概念ですか?
A いいえ!ライフタイムは完全にコンパイル時の概念です。
Q 「a」の「a」は何を意味しますか?
A 単なる名前です。a、b、cといった表記を使うのが慣例となっています。
Q なぜ、ほとんどのRustコードではライフタイムの注釈が表示されないのですか?
A ライフタイム省略ルールがあるためです。このルールにより、コンパイラが自動的にライフタイムを推論してくれます。
Q 入力参照「a」の存続期間と戻り値「a」の存続期間の関係はどのようなものですか?
A 両者の存続期間の共通部分となります。
Q いつライフサイクルを自分で定義する必要があるのですか?
A ケースは3つだけです。関数が複数の入力参照を受け取り、参照を返す場合、構造体に参照が含まれている場合、および関連型に制約を課すトレイトを実装する場合です。

📖 まとめ


📝 練習問題

  1. 難易度 ⭐:既存のRustコード(第9課の例など)を読み、ライフタイム省略ルールのおかげで手動でのアノテーションが不要になっている関数を特定してください。
  2. 難易度 ⭐⭐⭐: 2つの文字列のうち短い方を返す関数 fn shortest<'a>(x: &'a str, y: &'a str) -> &'a str を作成してください。main では、この関数を呼び出すために、有効期間が異なる変数を作成してください。
  3. 難易度 ⭐⭐⭐⭐: 型 &'a str のフィールド title を含む構造体 Book<'a> を定義してください。タイトルの最初の単語を返すメソッド fn first_word(&self) -> &str を実装する impl ブロックを作成してください。その使用例を main で示してください。
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%