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);
}使い分け
すべての参照が同じスコープで問題ない場合や、分ける必要がない場合は、単一の注釈を選ぶ。シンプルに保ち、複雑さを増やさずに済む。
参照ごとのスコープがはっきり違い、それを正確に表さないとエラーになる場合や、柔軟に扱いたい場合は、複数の注釈を選ぶ。たとえば、一部の参照だけを関数の戻り値として返したい場合である。