TL;DR

  • 単一のライフタイム注釈を持つ構造体は、すべての参照を最も短い参照の生存期間に合わせてしまう。
  • 複数のライフタイム注釈を持つ構造体は、参照ごとに生存期間を分けられるので、一部の参照だけを長く生かす設計ができる。

内容

項目14を読んでいて混乱してきたので、2 種類の構造体の違いをまとめる。

// 単一のライフタイム注釈を持つ構造体
struct Single<'a> {
    x: &'a str,
    y: &'a str,
}
 
// 複数のライフタイム注釈を持つ構造体
struct Multi<'a, 'b> {
    x: &'a str,
    y: &'b str,
}

同じ点

どちらも、構造体が持つすべての参照が有効な(ドロップされていない)間だけ、構造体も有効である。参照が 1 つでも無効になれば構造体全体が使えなくなるように、コンパイラが検査する。

違う点

単一の注釈では、2 つの参照がどちらも 'a に束縛される。そのため、片方のスコープが短いと、もう片方も同じだけ短いとみなされる。最も短い参照に合わせてしまうので、意図せず生存期間が縮まりやすい。

複数の注釈では、フィールドごとに生存期間を独立させられる。長いほうは長く、短いほうは短く表現できるので、「片方だけ長く生かしたい」「返すのはこちらだけ」といった使い方に向く。

次のコードは、単一の注釈ではコンパイルエラーになる。

fn single_return<'a>(s1: &'a str, s2: &'a str) -> &'a str {
    let st = Single { x: s1, y: s2 };
    st.x
}
 
fn main() {
    let outer = String::from("outer");
    let ret;
    {
        let inner = String::from("inner");
        ret = single_return(&outer, &inner);
        // 'a は outer と inner の両方を含むが、
        // 先にドロップされる inner の短さに合わせられる
    }
    // ret はダングリング参照とみなされるためエラー
    println!("{}", ret);
}

複数の注釈にすると、同じことができる。

fn multi_return<'a, 'b>(s1: &'a str, s2: &'b str) -> &'a str {
    let st = Multi { x: s1, y: s2 };
    st.x
}
 
fn main() {
    let outer = String::from("outer");
    let ret;
    {
        let inner = String::from("inner");
        ret = multi_return(&outer, &inner);
        // 'a と 'b は別の生存期間なので、
        // 戻り値は 'a に対応する outer だけに依存すると判断される
    }
    // 戻り値は 'a(outer)の参照なので OK
    println!("{}", ret);
}

使い分け

すべての参照が同じスコープで問題ない場合や、分ける必要がない場合は、単一の注釈を選ぶ。シンプルに保ち、複雑さを増やさずに済む。

参照ごとのスコープがはっきり違い、それを正確に表さないとエラーになる場合や、柔軟に扱いたい場合は、複数の注釈を選ぶ。たとえば、一部の参照だけを関数の戻り値として返したい場合である。

キーワード