Rustライフタイム完全解説:アノテーション、省略規則、面接対策 2026

Rustのライフタイムを徹底解説。ライフタイムアノテーション、省略規則、借用チェッカーの仕組みを実例で学び、技術面接に備える。

Rustライフタイム完全解説:アノテーション、省略規則、面接対策 2026

Rustのライフタイムは、参照がどれだけの期間有効であるかをコンパイラが追跡するための仕組みである。ガベージコレクションを持つ言語がランタイムでメモリ管理を行うのに対し、Rustの借用チェッカーはコンパイル時に参照の有効性を検証する。これにより、コードが実行される前にメモリに関するバグの多くを排除できる。

面接での模範解答

Rustにおけるライフタイムとは、参照が有効である期間を表すコンパイル時の構成要素である。借用チェッカーはライフタイムを使用して、参照が参照先のデータより長生きしないことを保証し、ランタイムオーバーヘッドなしでダングリングポインタを防止する。

ライフタイムがメモリ上で表すもの

ライフタイムは値がどれだけ存在するかではなく、値への参照がどれだけ有効であるかを記述する。Rust内のすべての参照は、アノテーションが省略されていても、ライフタイムを持っている。

コンパイラが拒否する以下の関数を考えてみる。

dangling_reference.rsrust
fn create_dangling() -> &String {
    let s = String::from("hello");
    &s  // ERROR: `s` is dropped at end of function
}

String型のsは関数スコープ内にのみ存在する。関数が戻ると、Stringのメモリが解放されるため、その参照を返すとダングリングポインタが生成される。借用チェッカーはこれをコンパイル時に検出する。

修正方法としては、所有権のある値を返すか、参照先のデータが関数呼び出しより長く存続することを保証する必要がある。

valid_return.rsrust
// Option 1: Return owned value
fn create_owned() -> String {
    String::from("hello")
}

// Option 2: Reference data that outlives the function
fn first_word(s: &str) -> &str {
    s.split_whitespace().next().unwrap_or("")
}

first_wordでは、返される&strが入力sから借用しているため、sが有効である限り有効なままとなる。呼び出し側が入力のライフタイムを制御する。

ライフタイムアノテーションの構文と意味論

明示的なライフタイムアノテーションは'a'bなどの構文を使用する。これらはコンパイラへの参照の存続期間についての指示ではなく、複数の参照のライフタイム間の関係を記述するものである。

Rustリファレンスでは、ライフタイムアノテーションを、参照が互いに相対的にどれだけ有効でなければならないかを制約するジェネリックパラメータとして定義している。

lifetime_annotations.rsrust
// Both inputs and output share the same lifetime 'a
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 string");
    let result;
    {
        let string2 = String::from("short");
        result = longest(&string1, &string2);
        println!("Longest: {}", result);  // Valid: both strings alive
    }
    // println!("{}", result);  // ERROR: string2 dropped
}

アノテーション'aはコンパイラに次のことを伝える:返される参照はxyのライフタイムの交差部分の間有効である。string2の方がライフタイムが短いため、string2がドロップされた後にresultを使用することはできない。

複数の異なるライフタイムはより複雑な関係を表現する。

multiple_lifetimes.rsrust
// Output tied only to first parameter's lifetime
fn first_only<'a, 'b>(x: &'a str, _y: &'b str) -> &'a str {
    x
}

fn main() {
    let owned = String::from("owned");
    let result;
    {
        let temporary = String::from("temporary");
        result = first_only(&owned, &temporary);
    }
    // result still valid: only depends on owned
    println!("{}", result);
}

Rust 2024におけるライフタイム省略規則

コンパイラはアノテーションが省略された場合、3つの省略規則を適用してライフタイムを推論する。Rustonomiconに文書化されているこれらの規則は、安全性を犠牲にすることなくボイラープレートを削減する。

3つの省略規則
  1. 各入力参照は独自のライフタイムパラメータを取得する
  2. 入力ライフタイムが1つだけ存在する場合、それがすべての出力参照に適用される
  3. &selfまたは&mut selfが存在する場合、そのライフタイムがすべての出力参照に適用される

これらの規則は最も一般的なパターンを処理する。

elision_examples.rsrust
// Rule 1: Each input gets own lifetime
fn takes_two(x: &str, y: &str) {}
// Compiler reads: fn takes_two<'a, 'b>(x: &'a str, y: &'b str) {}

// Rule 2: Single input lifetime propagates to output  
fn first_char(s: &str) -> &str {
    &s[0..1]
}
// Compiler reads: fn first_char<'a>(s: &'a str) -> &'a str

// Rule 3: &self lifetime propagates to output
impl Parser {
    fn peek(&self) -> &Token {
        &self.tokens[self.position]
    }
    // Compiler reads: fn peek<'a>(&'a self) -> &'a Token
}

省略が失敗すると、コンパイラは明示的なアノテーションを要求する。これは、複数の入力から派生した参照を返す関数で最も頻繁に発生する。

elision_fails.rsrust
// ERROR: Can't determine output lifetime
fn ambiguous(x: &str, y: &str) -> &str {
    if x.len() > y.len() { x } else { y }
}

// FIX: Explicit annotation resolves ambiguity
fn unambiguous<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

構造体のライフタイムとoutlives関係

参照を含む構造体は、借用されたデータが構造体より長く存続しなければならないことを表現するためにライフタイムアノテーションが必要である。

struct_lifetimes.rsrust
struct Excerpt<'a> {
    text: &'a str,
}

impl<'a> Excerpt<'a> {
    // new borrows from input, so Excerpt can't outlive the source
    fn new(source: &'a str, start: usize, end: usize) -> Self {
        Excerpt { text: &source[start..end] }
    }
    
    // level() returns owned data, no lifetime in signature
    fn level(&self) -> u32 {
        self.text.len() as u32 / 10
    }
}

fn main() {
    let novel = String::from("Call me Ishmael. Some years ago...");
    let excerpt = Excerpt::new(&novel, 0, 16);
    println!("Excerpt: {}", excerpt.text);
}  // novel dropped, but excerpt already out of scope

Excerpt<'a>'aは、構造体の有効性を借用されたtextのライフタイムに結び付ける。ソースStringがドロップされた後にExcerptを使用しようとすると、コンパイルエラーが発生する。

複数の参照を持つ構造体では、それぞれが異なるライフタイムを持つことがある。

multiple_struct_lifetimes.rsrust
struct Comparison<'a, 'b> {
    baseline: &'a str,
    candidate: &'b str,
}

impl<'a, 'b> Comparison<'a, 'b> {
    fn new(baseline: &'a str, candidate: &'b str) -> Self {
        Comparison { baseline, candidate }
    }
    
    fn baseline_only(&self) -> &'a str {
        self.baseline
    }
}

Rustの面接対策はできていますか?

インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。

'staticライフタイムとその使用場面

'staticライフタイムは、プログラム実行全体にわたって存続するデータを示す。文字列リテラルはバイナリに埋め込まれているため、このライフタイムを持つ。

static_lifetime.rsrust
let s: &'static str = "I live forever";

// Constants are implicitly 'static
const CONFIG_VERSION: &str = "2.0.0";

// Thread-safe globals require 'static
static COUNTER: AtomicU64 = AtomicU64::new(0);

一般的なパターンとしてT: 'static境界があるが、開発者を混乱させることが多い。この境界はT'staticでない参照を含まないことを意味し、Tが参照でなければならないことを意味しない。

static_bound.rsrust
use std::thread;

fn spawn_task<T: Send + 'static>(data: T) {
    thread::spawn(move || {
        // data moved into thread, must live independently
        println!("Processing in thread");
    });
}

fn main() {
    let owned = String::from("owned data");
    spawn_task(owned);  // OK: String is 'static (owns its data)
    
    let reference = "borrowed";
    // spawn_task(reference);  // OK: &'static str
}

スレッドはスポーン元の関数より長く存続する可能性があるため、スレッドに移動されるデータはスタックローカル変数への参照を含んではならない。

一般的なライフタイムエラーとその解決策

いくつかのパターンが開発者を常に悩ませる。これらを理解することでデバッグが加速する。

ローカル変数への参照を返す:

error_local_ref.rsrust
fn bad_split(text: &str, delimiter: char) -> (&str, &str) {
    let parts: Vec<&str> = text.split(delimiter).collect();
    (parts[0], parts[1])  // OK: parts[i] borrow from text, not from Vec
}

fn truly_bad() -> &str {
    let local = String::from("local");
    &local  // ERROR: local dropped at end of function
}

ライフタイムが一致しない構造体に参照を格納する:

error_struct_lifetime.rsrust
struct Cache {
    data: String,
    // view: &str,  // ERROR: needs lifetime parameter
}

// Self-referential structs require unsafe or crates like ouroboros
struct CacheWithView<'a> {
    data: String,
    view: Option<&'a str>,  // Can't point to self.data safely
}

フィールドが別のフィールドを参照する自己参照構造体は、unsafeコードを伴うPinまたはouroborosのようなクレートが必要である。Rustスマートポインタモジュールでは、これらの高度なパターンを扱っている。

トレイト実装でのライフタイム境界:

trait_lifetime_bounds.rsrust
trait Processor {
    fn process<'a>(&self, input: &'a str) -> &'a str;
}

struct Prefixer {
    prefix: String,
}

impl Processor for Prefixer {
    // Can't return &format!(...) - would be temporary
    fn process<'a>(&self, input: &'a str) -> &'a str {
        input  // Must return input or part of it
    }
}

ジェネリックライフタイムのための高階トレイト境界(HRTB)

高階トレイト境界は、型が特定のライフタイムだけでなく任意のライフタイムでトレイトを満たす必要があることを表現するためにfor<'a>構文を使用する。

hrtb.rsrust
use std::fmt::Debug;

// F must be callable with any lifetime 'a
fn apply_to_refs<F>(f: F)
where
    F: for<'a> Fn(&'a str) -> &'a str,
{
    let owned = String::from("test");
    let result = f(&owned);
    println!("{}", result);
}

fn identity(s: &str) -> &str { s }

fn main() {
    apply_to_refs(identity);
}

HRTBはクロージャを受け入れるAPIや、様々なライフタイムの参照で動作する必要があるRust async/awaitエコシステムで頻繁に登場する。

Rustライフタイムに関する面接質問

技術面接では、複数の深さでライフタイムの理解を調査する。これらの質問はRustポジションで定期的に出題される。

質問:このコードがコンパイルに失敗する理由は?

interview_q1.rsrust
fn get_str() -> &str {
    "hello"
}

回答: 戻り値の型には明示的なライフタイムアノテーションが必要である。文字列リテラルは'staticライフタイムを持つが、関数シグネチャはこれを表現していない。修正:fn get_str() -> &'static str

質問:このコードがコンパイルされる理由と、安全かどうかを説明せよ:

interview_q2.rsrust
fn longest<'a>(x: &'a str, _y: &str) -> &'a str {
    x
}

回答: 出力ライフタイムがxにのみ結び付けられているため、このコードはコンパイルされる。戻り値が_yに依存しないため、_yパラメータは任意のライフタイムを持つことができる。これは安全である:返される参照の有効性はxのライフタイムにのみ依存する。

質問:所有データと一緒に参照を格納しようとするとどうなるか?

interview_q3.rsrust
struct Config<'a> {
    name: String,
    description: &'a str,
}

回答: このパターンは有効だが、Configの使用方法を制約する。構造体はdescriptionが参照するものより長く存続できない。自身を参照する必要がある所有データについては、両方のフィールドにStringを使用するか、Rustの所有権と借用で扱っている技術を検討する。

質問:ライフタイムはトレイトオブジェクトとどのように相互作用するか?

interview_q4.rsrust
trait Formatter {
    fn format(&self, input: &str) -> String;
}

fn get_formatter<'a>() -> Box<dyn Formatter + 'a> {
    // ...
}

回答: トレイトオブジェクトはデフォルトで暗黙の'staticライフタイム境界を持つ。Box<dyn Formatter + 'a>と明示的に書くことで、トレイトオブジェクトがライフタイム'aの参照を含むことを許可する。明示的な境界がない場合、Box<dyn Formatter>Box<dyn Formatter + 'static>と等しい。

ライフタイムの変性:共変性と反変性

変性は、型がネストされているときにライフタイムがどのように関係するかを決定する。Rustの参照は以下の規則に従う。

  • &'a T'aに対して共変:より長いライフタイムがより短いものを代替できる
  • &'a mut TTに対して不変:型は正確に一致する必要がある
  • fn(&'a T)'aに対して反変:より短いライフタイムがより長いものを代替できる
variance.rsrust
fn covariant_example() {
    let s: &'static str = "static";
    let r: &str = s;  // OK: 'static lives longer than any 'a
}

fn invariant_example() {
    let mut vec: Vec<&'static str> = vec!["a"];
    // let s = String::from("local");
    // vec.push(&s);  // ERROR: &s is not &'static str
}

変性の理解は、参照を受け入れたり返したりするジェネリックAPIを設計する際に重要である。Rustのトレイトとジェネリクスの記事では、高度なジェネリックパターンを扱っている。

今すぐ練習を始めましょう!

面接シミュレーターと技術テストで知識をテストしましょう。

実践におけるライフタイムアノテーション:重要なポイント

  • ライフタイムは値の存在ではなく、参照の有効性を記述する。借用チェッカーはこれらを使用してコンパイル時にダングリングポインタを防止する。
  • 省略規則はほとんどのケースを自動的に処理する。明示的なアノテーションは、複数の入力から派生した参照を返す際に必要になる。
  • 構造体のライフタイムはoutlives関係を表現する:参照を含む構造体は、それらの参照のライフタイムでパラメータ化されなければならない。
  • ジェネリクスに対する'static境界は「'staticでない参照を含まない」ことを意味し、「参照でなければならない」ことを意味しない。
  • 自己参照構造体は、Pin、unsafeコード、またはヘルパークレートによる特別な処理が必要である。
  • 面接質問は、コードがコンパイルに失敗する理由と、ライフタイムアノテーションが有効性の保証をどのように変えるかの理解に焦点を当てる。
今日のチャレンジ

Rust のバグを見つけられますか

実際のコード、隠れたバグ、1日1回。アカウントなしで試せます。

Anthony Fillion-Maillet

執筆

Anthony Fillion-Maillet

SharpSkill 創業者

10 年以上フルスタック開発に携わっています。SharpSkill を運営し、ここで公開される内容に責任を負っています。

2026年8月27日 更新

共有

関連記事