ライフタイムで参照を検証する

第4章の「参照と借用」節で議論しなかった詳細の一つは、Rustにおいて参照は全てライフタイムを保持することであり、 ライフタイムとは、その参照が有効になるスコープのことです。多くの場合、型が推論されるように、 多くの場合、ライフタイムも暗黙的に推論されます。複数の型の可能性があるときには、型を注釈しなければなりません。 同様に、参照のライフタイムがいくつか異なる方法で関係することがある場合には注釈しなければなりません。 コンパイラは、ジェネリックライフタイム引数を使用して関係を注釈し、実行時に実際の参照が確かに有効であることを保証することを要求するのです。

ライフタイムの概念は、ほかのプログラミング言語の道具とはどこか異なり、議論はあるでしょうが、 ライフタイムがRustで一番際立った機能になっています。この章では、ライフタイムの全てを講義しないものの、 ライフタイム記法と遭遇する可能性のある一般的な手段を議論するので、概念に馴染めます。 もっと詳しく知るには、第19章の「高度なライフタイム」節を参照されたし。

ライフタイムでダングリング参照を回避する

ライフタイムの主な目的は、ダングリング参照を回避することであり、ダングリング参照によりプログラムは、 参照することを意図したデータ以外のデータを参照してしまいます。リスト10-17のプログラムを考えてください。 これには、外側のスコープと内側のスコープが含まれています。

{
    let r;

    {
        let x = 5;
        r = &x;
    }

    println!("r: {}", r);
}

リスト10-17: 値がスコープを抜けてしまった参照を使用しようとする

注釈: リスト10-17や10-18、10-24では、変数に初期値を与えずに宣言しているので、変数名は外側のスコープに存在します。 初見では、これはRustにはnull値が存在しないということと衝突しているように見える可能性があります。 しかしながら、値を与える前に変数を使用しようとすれば、コンパイルエラーになり、 確かにRustではnull値は許可されないことを示します。

外側のスコープで初期値なしのrという変数を宣言し、内側のスコープで初期値5のxという変数を宣言しています。 内側のスコープ内で、rの値をxへの参照にセットしようとしています。それから内側のスコープが終わり、 rの値を出力しようとしています。rが参照している値が使おうとする前にスコープを抜けるので、 このコードはコンパイルできません。こちらがエラーメッセージです:

error[E0597]: `x` does not live long enough
(エラー: `x`の生存期間が短すぎます)
  --> src/main.rs:7:5
   |
6  |         r = &x;
   |              - borrow occurs here
   |              (借用はここで起きています)
7  |     }
   |     ^ `x` dropped here while still borrowed
   |     (`x`は借用されている間にここでドロップされました)
...
10 | }
   | - borrowed value needs to live until here
   | (借用された値はここまで生きる必要があります)

変数xの「生存期間が短すぎます。」原因は内側のスコープが7行目で終わった時点でxがスコープを抜けるからです。 ですが、rはまだ、外側のスコープに対して有効です; スコープが大きいので、「長生きする」と言います。 Rustで、このコードが動くことを許可していたら、rxがスコープを抜けた時に解放されるメモリを参照していることになり、 rで行おうとするいかなることもちゃんと動かないでしょう。では、どうやってコンパイラはこのコードが無効であると決定しているのでしょうか? 借用精査機(borrow checker)を使用しています。

借用精査機

Rustコンパイラには、スコープを比較して全ての借用が有効であるかを決定する借用精査機があります。 リスト10-18は、リスト10-17と同じコードを示していますが、変数のライフタイムを表示する注釈が付いています:

{
    let r;                // ---------+-- 'a
                          //          |
    {                     //          |
        let x = 5;        // -+-- 'b  |
        r = &x;           //  |       |
    }                     // -+       |
                          //          |
    println!("r: {}", r); //          |
}                         // ---------+

リスト10-18: それぞれ'a'bと名付けられたrxのライフタイムの注釈

ここで、rのライフタイムは'axのライフタイムは'bで注釈しました。ご覧の通り、 内側の'bブロックの方が、外側の'aライフタイムブロックよりはるかに小さいです。 コンパイル時に、コンパイラは2つのライフタイムのサイズを比較し、r'aのライフタイムだけれども、 'bのライフタイムのメモリを参照していると確認します。'b'aよりも短いので、プログラムは拒否されます: 参照の被写体が参照ほど長生きしないのです。

リスト10-19でコードを修正したので、ダングリング参照はなくなり、エラーなくコンパイルできます。


# #![allow(unused_variables)]
#fn main() {
{
    let x = 5;            // ----------+-- 'b
                          //           |
    let r = &x;           // --+-- 'a  |
                          //   |       |
    println!("r: {}", r); //   |       |
                          // --+       |
}                         // ----------+
#}

リスト10-19: データのライフタイムが参照より長いので、有効な参照

ここでxのライフタイムは'bになり、今回の場合'aよりも大きいです。つまり、 コンパイラはxが有効な間、rの参照も常に有効になることを把握しているので、rxを参照できます。

今や、参照のライフタイムがどこにあり、コンパイラがライフタイムを解析して参照が常に有効であることを保証する仕組みがわかったので、 関数の文脈でジェネリックな引数と戻り値のライフタイムを探求しましょう。

関数のジェネリックなライフタイム

2つの文字列スライスのうち、長い方を返す関数を書きましょう。この関数は、 2つの文字列スライスを取り、1つの文字列スライスを返します。longest関数の実装完了後、 リスト10-20のコードは、The logest string is abcdと出力するはずです。

ファイル名: src/main.rs

fn main() {
    let string1 = String::from("abcd");
    let string2 = "xyz";

    let result = longest(string1.as_str(), string2);
    // 最長の文字列は、{}です
    println!("The longest string is {}", result);
}

リスト10-20: longest関数を呼び出して2つの文字列スライスのうち長い方を探すmain関数

関数に取ってほしい引数が文字列スライス、つまり参照であることに注意してください。 何故なら、longest関数に引数の所有権を奪ってほしくないからです。 この関数にStringのスライス(変数string1に格納されている型)と文字列リテラル(変数string2が含むもの)を受け取らせたいのです。

リスト10-20で使用している引数が求めているものである理由についてもっと詳しい議論は、 第4章の「引数としての文字列スライス」節をご参照ください。

リスト10-21に示したようにlongetst関数を実装しようとしたら、コンパイルできないでしょう。

ファイル名: src/main.rs

fn longest(x: &str, y: &str) -> &str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

リスト10-21: 2つの文字列スライスのうち長い方を返すけれども、コンパイルできないlongest関数の実装

代わりに、以下のようなライフタイムに言及するエラーが出ます:

error[E0106]: missing lifetime specifier
(エラー: ライフタイム指定子が不足しています)
 --> src/main.rs:1:33
  |
1 | fn longest(x: &str, y: &str) -> &str {
  |                                 ^ expected lifetime parameter
  |                                   (ライフタイム引数が予想されます)
  |
  = help: this function's return type contains a borrowed value, but the
signature does not say whether it is borrowed from `x` or `y`
  (助言: この関数の戻り値型は借用された値を含んでいますが、
シグニチャは、それが`x`か`y`由来のものなのか宣言していません)

助言テキストが戻り値の型はジェネリックなライフタイム引数である必要があると明かしています。 というのも、返している参照がxyを参照しているかコンパイラにはわからないからです。 この関数の本体のifブロックはxへの参照を返し、elseブロックはyへの参照を返すので、 実際、どちらか私たちにもわかりません!

この関数を定義する際、この関数に渡される具体的な値がわからないので、ifケースか、elseケースが実行されるか、わからないのです。 また、渡される参照の具体的なライフタイムもわからないので、リスト10-18と10-19で、 返す参照が常に有効であるかを決定したようにスコープを見ることもできないのです。 借用精査機もこれを決定することはできません。xyのライフタイムがどう戻り値のライフタイムと関係するかわからないからです。 このエラーを修正するには、借用精査機が解析を実行できるように、参照間の関係を定義するジェネリックなライフタイム引数を追加します。

ライフタイム注釈記法

ライフタイム注釈は、いかなる参照の生存期間も変えることはありません。シグニチャがジェネリックな型引数を指定している時に、 関数があらゆる型を受け入れるのと全く同様に、ジェネリックなライフタイム引数を指定することで関数は、 あらゆるライフタイムの参照を受け入れるのです。ライフタイム注釈は、ライフタイムに影響することなく、 複数の参照のライフタイムのお互いの関係を記述します。

ライフタイム注釈は、少しだけ不自然な記法です: ライフタイム引数の名前はアポストロフィー(')で始まらなければならず、 通常全部小文字で、ジェネリック型のようにとても短いです。多くの人は、'aという名前を使います。 ライフタイム引数注釈は、参照の&の後に配置し、注釈と参照の型を区別するために空白を1つ使用します。

例を挙げましょう: ライフタイム引数なしのi32への参照、'aというライフタイム引数付きのi32への参照、 これもライフタイム'a付きi32への可変参照です。

            // (ただの)参照
&i32        // a reference
            // 明示的なライフタイム付きの参照
&'a i32     // a reference with an explicit lifetime
            // 明示的なライフタイム付きの可変参照
&'a mut i32 // a mutable reference with an explicit lifetime

1つのライフタイム注釈それだけでは、大して意味はありません。注釈は、複数の参照のジェネリックなライフタイム引数が、 お互いにどう関係するかをコンパイラに指示することを意図しているからです。例えば、 ライフタイム'a付きのi32への参照となる引数firstのある関数があるとしましょう。 この関数にはさらに、'aのライフタイム付きのi32への別の参照となるsecondという別の引数もあります。 ライフタイム注釈は、firstsecondの参照がどちらもジェネリックなライフタイムと同じだけ生きることを示唆します。

関数シグニチャにおけるライフタイム注釈

さて、longest関数の文脈でライフタイム注釈を調査しましょう。ジェネリックな型引数同様、 関数名と引数リストの間、山カッコの中にジェネリックなライフタイム引数を宣言する必要があります。 このシグニチャで表現したい制約は、引数の全参照と戻り値が同じライフタイムになることです。 ライフタイムを'aと名付け、それから各参照に追記します。リスト10-22に示したように。

ファイル名: src/main.rs


# #![allow(unused_variables)]
#fn main() {
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}
#}

リスト10-22: シグニチャの全参照が同じライフタイム'aになると指定したlongest関数の定義

このコードはコンパイルでき、リスト10-20のmain関数とともに使用したら、欲しい結果になるはずです。

これで関数シグニチャは、何らかのライフタイム'aに対して、関数は2つの引数を取り、 どちらも少なくともライフタイム'aと同じだけ生きる文字列スライスであるとコンパイラに教えています。 また、この関数シグニチャは、関数から返る文字列スライスも少なくともライフタイム'aと同じだけ生きると、 コンパイラに教えています。これらの制約は、コンパイラに強制してほしいものです。 この関数シグニチャでライフタイム引数を指定する時、渡されたり、返したりしたいかなる値のライフタイムも変更していないことを思い出してください。 むしろ、借用精査機は、これらの制約を支持しない値全てを拒否するべきと指定しています。 longest関数は、正確にxyの生存期間を知る必要はなく、何かのスコープが'aに代替され、 このシグニチャを満足することだけ知っている必要があることに注意してください。

関数でライフタイムを注釈する際、注釈は関数シグニチャに(はま)り、 関数本体には嵌りません。コンパイラは、なんの助けもなく、関数内のコードを解析できます。しかしながら、 関数に関数外やからの参照がある場合、コンパイラは引数や戻り値のライフタイムをそれだけではじき出すことはほとんど不可能になります。 ライフタイムは、関数が呼び出される度に異なる可能性があります。このために、手動でライフタイムを注釈する必要があるのです。

具体的な参照をlongestに渡すと、'aを代替する具体的なライフタイムは、yのスコープと被さるxのスコープの一部になります。 言い換えると、ジェネリックなライフタイム'aは、xyのライフタイムのうち、小さい方に等しい具体的なライフタイムになるのです。 返却される参照を同じライフタイム引数'aで注釈したので、返却される参照もxyのライフタイムの小さい方と同じだけ有効になるでしょう。

ライフタイム注釈が異なる具体的なライフタイムになる参照を渡すことでlongest関数を制限する方法を見ましょう。 リスト10-23は、率直な例です。

ファイル名: src/main.rs

# 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 is long");

    {
        let string2 = String::from("xyz");
        let result = longest(string1.as_str(), string2.as_str());
        println!("The longest string is {}", result);
    }
}

リスト10-23: 異なる具体的なライフタイムのString値への参照でlongest関数を使用する

この例において、string1は外側のスコープの終わりまで有効で、string2は内側のスコープの終わりまで有効、 そしてresultは内側のスコープの終わりまで有効な何かを参照しています。このコードを実行すると、 借用精査機がこのコードに賛成するのがわかるでしょう。要するに、コンパイルでき、 The longest string is long string is longと出力するのです。

次に、resultの参照のライフタイムが2つの引数の小さい方のライフタイムになることを示す例を試しましょう。 result変数の宣言を内側のスコープの外に移すものの、result変数への代入はstring2のスコープ内に残したままにします。 それからresultを使用するprintln!を内側のスコープの外、内側のスコープが終わった後に移動します。 リスト10-24のコードはコンパイルできません。

ファイル名: src/main.rs

fn main() {
    let string1 = String::from("long string is long");
    let result;
    {
        let string2 = String::from("xyz");
        result = longest(string1.as_str(), string2.as_str());
    }
    println!("The longest string is {}", result);
}

リスト10-24: string2がスコープを抜けてからresultを使用しようとする

このコードのコンパイルを試みると、こんなエラーになります:

error[E0597]: `string2` does not live long enough
  --> src/main.rs:15:5
   |
14 |         result = longest(string1.as_str(), string2.as_str());
   |                                            ------- borrow occurs here
15 |     }
   |     ^ `string2` dropped here while still borrowed
16 |     println!("The longest string is {}", result);
17 | }
   | - borrowed value needs to live until here

このエラーは、resultprintln!文に対して有効になるために、string2が外側のスコープの終わりまで有効である必要があることを示しています。 関数引数と戻り値のライフタイムを同じライフタイム引数'aで注釈したので、コンパイラはこのことを知っています。

人間からしたら、このコードを見てstring1string2よりも長いことが確認でき、 故にresultstring1への参照を含んでいます。まだstring1はスコープを抜けていないので、 それでもstring1への参照はprintln!にとって有効でしょう。ですが、コンパイラはこの場合、 参照が有効であると見なせません。longest関数から返ってくる参照のライフタイムは、 渡した参照のうちの小さい方と同じだとコンパイラに指示しました。それ故に、 借用精査機は、リスト10-24のコードを無効な参照がある可能性があるとして許可しないのです。

試しに値やlongest関数に渡される参照のライフタイムや返される参照の使用法が異なる実験をもっと企ててみてください。 自分の実験がコンパイル前に借用精査機を通るかどうか仮説を立ててください; そして、正しいか確かめてください!

ライフタイムの観点で思考する

ライフタイム引数を指定する必要のある手段は、関数が行っていることによります。例えば、 longest関数の実装を最長の文字列スライスではなく、常に最初の引数を返すように変更したら、 y引数に対してライフタイムを指定する必要はなくなるでしょう。以下のコードはコンパイルできます:

ファイル名: src/main.rs


# #![allow(unused_variables)]
#fn main() {
fn longest<'a>(x: &'a str, y: &str) -> &'a str {
    x
}
#}

この例では、引数xと戻り値に対してライフタイム引数'aを指定しましたが、引数yには指定していません。 yのライフタイムはxや戻り値のライフタイムとは何の関係もないからです。

関数から参照を返す際、戻り値型のライフタイム引数は、引数のうちどれかのライフタイム引数と一致する必要があります。 返される参照が引数のどれかを参照していなければ、この関数内で生成された値を参照しているに違いなく、 これは、その値が関数の末端でスコープを抜けるので、ダングリング参照になるでしょう。 コンパイルできないこのlongest関数の未遂の実装を考えてください:

ファイル名: src/main.rs

fn longest<'a>(x: &str, y: &str) -> &'a str {
    // 本当に長い文字列
    let result = String::from("really long string");
    result.as_str()
}

ここでは、たとえ、戻り値型にライフタイム引数'aを指定していても、戻り値のライフタイムは、 引数のライフタイムと全く関係がないので、この実装はコンパイルできないでしょう。 こちらが、得られるエラーメッセージです:

error[E0597]: `result` does not live long enough
 --> src/main.rs:3:5
  |
3 |     result.as_str()
  |     ^^^^^^ does not live long enough
4 | }
  | - borrowed value only lives until here
  |
note: borrowed value must be valid for the lifetime 'a as defined on the
function body at 1:1...
(注釈: 借用された値は、関数本体1行目1文字目で定義されているようにライフタイム'aに対して有効でなければなりません)
 --> src/main.rs:1:1
  |
1 | / fn longest<'a>(x: &str, y: &str) -> &'a str {
2 | |     let result = String::from("really long string");
3 | |     result.as_str()
4 | | }
  | |_^

問題は、resultlongest関数の末端でスコープを抜け、片付けられてしまうことです。 また、関数からresultを返そうともしています。ダングリング参照を変えるであろうライフタイム引数を指定する手段はなく、 コンパイラは、ダングリング参照を生成させてくれません。今回の場合、最善の修正案は、 呼び出し元の関数が値の片付けに責任を持てるよう、参照ではなく所有されたデータ型を返すことでしょう。

究極的にライフタイム記法は、関数のいろんな引数と戻り値のライフタイムを接続することに関するのです。 一旦、繋がりができたら、メモリ安全な処理を許可するのに十分な情報がコンパイラにはあり、 ダングリングポインタを生成するであろう処理を不認可し、さもなくばメモリ安全性を侵害するのです。

構造体定義のライフタイム注釈

ここまで、所有された型を保持する構造体だけを定義してきました。構造体に参照を保持させることもできますが、 その場合、構造体定義の全参照にライフタイム注釈を付け加える必要があるでしょう。 リスト10-25には、文字列スライスを保持するImportantExcerpt(重要な一節)という構造体があります。

ファイル名: src/main.rs

struct ImportantExcerpt<'a> {
    part: &'a str,
}

fn main() {
    // 僕をイシュマエルとお呼び。何年か前・・・
    let novel = String::from("Call me Ishmael. Some years ago...");
    let first_sentence = novel.split('.')
        .next()
        .expect("Could not find a '.'");  // '.'が見つかりませんでした
    let i = ImportantExcerpt { part: first_sentence };
}

リスト10-25: 参照を含む構造体なので、定義にライフタイム注釈が必要

この構造体には文字列スライスを保持する1つのフィールド、partがあり、これは参照です。 ジェネリックなデータ型同様、構造体名の後、山カッコの中にジェネリックなライフタイム引数の名前を宣言するので、 構造体定義の本体でライフタイム引数を使用できます。この注釈は、ImportantExcerptのインスタンスが、 partフィールドに保持している参照よりも長生きしないことを意味します。

ここのmain関数は、変数novelに所有されるStringの最初の文への参照を保持するImportantExcerptインスタンスを生成しています。 novelのデータは、ImportantExcerptインスタンスが作られる前に存在しています。 加えて、ImportantExcerptがスコープを抜けるまでnovelはスコープを抜けないので、 ImportantExcerptインスタンスの参照は有効なのです。

ライフタイム省略

全参照にはライフタイムがあり、参照を使用する関数や構造体にはライフタイム引数を指定する必要があることを学びました。 ですが、リスト4-9にとある関数があり、リスト10-26に再度示しましたが、 これは、ライフタイム注釈なしでコンパイルできました。

ファイル名: src/lib.rs


# #![allow(unused_variables)]
#fn main() {
fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }

    &s[..]
}
#}

リスト10-26: 引数と戻り値型が参照であるにも関わらず、ライフタイム注釈なしでコンパイルできた リスト4-9で定義した関数

この関数がライフタイム注釈なしでコンパイルできた理由は、歴史的なものです: 昔のバージョンのRust(1.0以前)では、 全参照に明示的なライフタイムが必要だったので、このコードはコンパイルできませんでした。 その頃、関数シグニチャはこのように記述されていたのです:

fn first_word<'a>(s: &'a str) -> &'a str {

多くのRustコードを書いた後、Rustチームは、Rustプログラマが特定の場面では、 何度も何度も同じライフタイム注釈を入力することを発見しました。これらの場面は予測可能で、 いくつかの決定的なパターンに従っていました。開発者はこのパターンをコンパイラのコードに落とし込んだので、 このような場面には借用精査機がライフタイムを推論できるようになり、明示的な注釈を必要としなくなったのです。

他の決定的なパターンが出現し、コンパイラに追加されることもあり得るので、このRustの歴史は関係があります。 将来的に、さらに少数のライフタイム注釈しか必要にならない可能性もあります。

コンパイラの参照解析に落とし込まれたパターンは、ライフタイム省略規則と呼ばれます。 これらはプログラマが従う規則ではありません; コンパイラが考慮する一連の特定のケースであり、 自分のコードがこのケースに当てはまれば、ライフタイムを明示的に書く必要はなくなります。

省略規則は、完全な推論を提供しません。コンパイラが決定的に規則を適用できるけれども、 参照が保持するライフタイムに関してそれでも曖昧性があるなら、コンパイラは、残りの参照がなるべきライフタイムを推論しません。 この場合、推論ではなく、コンパイラは、参照がお互いにどう関係するかを指定するライフタイム注釈を追記することで、 解決できるエラーを与えます。

関数やメソッドの引数のライフタイムは、入力ライフタイムと呼ばれ、 戻り値のライフタイムは出力ライフタイムと称されます。

コンパイラは3つの規則を活用し、明示的な注釈がない時に、参照がどんなライフタイムになるかを計算します。 最初の規則は入力ライフタイムに適用され、2番目と3番目の規則は出力ライフタイムに適用されます。 コンパイラが3つの規則の最後まで到達し、それでもライフタイムを割り出せない参照があったら、 コンパイラはエラーで停止します。

最初の規則は、参照である各引数は、独自のライフタイム引数を得るというものです。換言すれば、 1引数の関数は、1つのライフタイム引数を得るということです: fn foo<'a>(x: &'a i32); 2つ引数のある関数は、2つの個別のライフタイム引数を得ます: fn foo<'a, 'b>(x: &'a i32, y: &'b i32); 以下同様。

2番目の規則は、1つだけ入力ライフタイム引数があるなら、そのライフタイムが全ての出力ライフタイム引数に代入されるというものです: fn foo<'a>(x: &'a i32) -> &'a i32

3番目の規則は、複数の入力ライフタイム引数があるけれども、メソッドなのでそのうちの一つが&self&mut selfだったら、 selfのライフタイムが全出力ライフタイム引数に代入されるというものです。 この3番目の規則により、必要なシンボルの数が減るので、メソッドが遥かに読み書きしやすくなります。

コンパイラになってみましょう。これらの規則を適用して、リスト10-26のfirst_word関数のシグニチャの参照のライフタイムが何か計算します。 シグニチャは、参照に紐づけられるライフタイムがない状態から始まります:

fn first_word(s: &str) -> &str {

そうして、コンパイラは最初の規則を適用し、各引数が独自のライフタイムを得ると指定します。 それを通常通り'aと呼ぶので、シグニチャはこうなります:

fn first_word<'a>(s: &'a str) -> &str {

1つだけ入力ライフタイムがあるので、2番目の規則を適用します。2番目の規則は、1つの入力引数のライフタイムが、 出力引数に代入されると指定するので、シグニチャはこうなります:

fn first_word<'a>(s: &'a str) -> &'a str {

もうこの関数シグニチャの全ての参照にライフタイムが付いたので、コンパイラは、 プログラマにこの関数シグニチャのライフタイムを注釈してもらう必要なく、解析を続行できます。

別の例に目を向けましょう。今回は、リスト10-21で取り掛かったときにはライフタイム引数はなかったlongest関数です:

fn longest(x: &str, y: &str) -> &str {

最初の規則を適用しましょう: 各引数が独自のライフタイムを得るのです。今回は、 1つではなく2つ引数があるので、ライフタイムも2つです:

fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str {

2つ以上入力ライフタイムがあるので、2番目の規則は適用されないとわかります。また3番目の規則も適用されません。 longestはメソッドではなく関数なので、どの引数もselfではないのです。3つの規則全部を適用した後、 まだ戻り値型のライフタイムが判明していません。このために、リスト10-21でこのコードをコンパイルしようとしてエラーになったのです: コンパイラは、ライフタイム省略規則全てを適用したけれども、シグニチャの参照全部のライフタイムを計算できなかったのです。

3番目の規則は本当にメソッドシグニチャでしか適用されないので、次にその文脈でライフタイムを観察し、 3番目の規則が、メソッドシグニチャであまり頻繁にライフタイムを注釈しなくても済むことを意味する理由を確認します。

メソッド定義におけるライフタイム注釈

構造体にライフタイムのあるメソッドを実装する際、リスト10-11で示したジェネリックな型引数と同じ記法を使用します。 ライフタイム引数を宣言し使用する場所は、構造体フィールドかメソッド引数と戻り値に関係するかによります。

構造体のフィールド用のライフタイム名は、implキーワードの後に宣言する必要があり、 それから構造体名の後に使用されます。そのようなライフタイムは構造体の型の一部になるからです。

implブロック内のメソッドシグニチャでは、参照は構造体のフィールドの参照のライフタイムに紐づくか、 独立している可能性があります。加えて、ライフタイム省略規則により、メソッドシグニチャでライフタイム注釈が必要なくなることがよくあります。 リスト10-25で定義したImportantExcerptという構造体を使用して、何か例を眺めましょう。

まず、唯一の引数がselfへの参照で戻り値がi32という何かへの参照ではないlevelというメソッドを使用します:


# #![allow(unused_variables)]
#fn main() {
# struct ImportantExcerpt<'a> {
#     part: &'a str,
# }
#
impl<'a> ImportantExcerpt<'a> {
    fn level(&self) -> i32 {
        3
    }
}
#}

impl後のライフタイム引数宣言と型名の後に使用するのは必須ですが、最初の省略規則のため、 selfへの参照のライフタイムを注釈する必要はありません。

3番目のライフタイム省略規則が適用される例はこちらです:


# #![allow(unused_variables)]
#fn main() {
# struct ImportantExcerpt<'a> {
#     part: &'a str,
# }
#
impl<'a> ImportantExcerpt<'a> {
    fn announce_and_return_part(&self, announcement: &str) -> &str {
        println!("Attention please: {}", announcement);
        self.part
    }
}
#}

2つ入力ライフタイムがあるので、コンパイラは最初のライフタイム省略規則を適用し、 &selfannouncementに独自のライフタイムを与えます。それから、 引数の1つが&selfなので、戻り値型は&selfのライフタイムを得て、 全てのライフタイムが説明されました。

静的ライフタイム

議論する必要のある1種の特殊なライフタイムが、'staticであり、これはプログラム全体の期間を示します。 文字列リテラルは全て'staticライフタイムになり、次のように注釈できます:


# #![allow(unused_variables)]
#fn main() {
// 静的ライフタイムを持ってるよ
let s: &'static str = "I have a static lifetime.";
#}

この文字列のテキストは、プログラムのバイナリに直接格納され、常に利用可能です。故に、全文字列リテラルのライフタイムは、 'staticなのです。

エラーメッセージで'staticライフタイムを使用することを勧められる可能性があります。 ですが、参照に対してライフタイムとして'staticを指定する前に、今ある参照が本当にプログラムの全期間生きるかどうか考えてください。 可能であっても、参照がそれだけの期間生きるかどうか考慮する可能性があります。 ほとんどの場合、問題は、ダングリング参照を生成しようとしているか、利用可能なライフタイムの不一致が原因です。 そのような場合、解決策はその問題を修正することであり、'staticライフタイムを指定することではありません。

ジェネリックな型引数、トレイト境界、ライフタイムを一度に

ジェネリックな型引数、トレイト境界、ライフタイムを指定する記法を全て1関数でちょっと眺めましょう!


# #![allow(unused_variables)]
#fn main() {
use std::fmt::Display;

fn longest_with_an_announcement<'a, T>(x: &'a str, y: &'a str, ann: T) -> &'a str
    where T: Display
{
    // アナウンス!
    println!("Announcement! {}", ann);
    if x.len() > y.len() {
        x
    } else {
        y
    }
}
#}

これがリスト10-22からの2つの文字列のうち長い方を返すlongest関数ですが、 ジェネリックな型Tannという追加の引数があり、これはwhere節で指定されているように、 Displayトレイトを実装するあらゆる型で埋めることができます。 この追加の引数は、関数が文字列スライスの長さを比較する前に出力されるので、 Displayトレイト境界が必要なのです。ライフタイムは1種のジェネリックなので、 ライフタイム引数'aとジェネリックな型引数Tが関数名の後、山カッコ内の同じリストに収まっています。

総括

いろんなことをこの章では講義しましたね!今やジェネリックな型引数、トレイトとトレイト境界、そしてジェネリックなライフタイム引数を知ったので、 多くの異なる場面で動くコードを繰り返しなく書く準備ができました。ジェネリックな型引数により、 コードを異なる型に適用させてくれます。トレイトとトレイト境界は、型がジェネリックであっても、 コードが必要とする振る舞いを持つことを保証します。ライフタイム注釈を活用して、 この柔軟なコードにダングリング参照が存在しないことを保証する方法を学びました。 さらにこの解析は全てコンパイル時に起こり、実行時のパフォーマンスには影響しません!

信じるかどうかは自由ですが、この章で議論した話題にはもっともっと学ぶべきことがあります: 第17章ではトレイトオブジェクトを議論し、これはトレイトを使用する別の手段です。 第19章では、ライフタイム注釈が関わるもっと複雑な筋書きと何か高度な型システムの機能を講義します。 ですが次は、Rustでテストを書く方法を学ぶので、コードがあるべき通りに動いていることを確かめられます。