ライフタイムで参照を検証する
第4章の「参照と借用」節で議論しなかった詳細の一つに、Rustにおいて参照は全てライフタイムを保持するということがあります。 ライフタイムとは、その参照が有効になるスコープのことです。多くの場合、型が推論されるように、 大体の場合、ライフタイムも暗黙的に推論されます。複数の型の可能性があるときには、型を注釈しなければなりません。 同様に、参照のライフタイムがいくつか異なる方法で関係することがある場合には注釈しなければなりません。 コンパイラは、ジェネリックライフタイム引数を使用して関係を注釈し、実行時に実際の参照が確かに有効であることを保証することを要求するのです。
ライフタイムの概念は、他のプログラミング言語の道具とはどこか異なり、間違いなく、 Rustで一番際立った機能になっています。この章では、ライフタイムの全てを講義しないものの、 ライフタイム記法と遭遇する可能性のある一般的な手段を議論するので、その概念に馴染めます。 もっと詳しく知るには、第19章の「高度なライフタイム」節を参照されたし。
ライフタイムでダングリング参照を回避する
ライフタイムの主な目的は、ダングリング参照を回避することです。ダングリング参照によりプログラムは、 参照するつもりだったデータ以外のデータを参照してしまいます。リスト10-17のプログラムを考えてください。 これには、外側のスコープと内側のスコープが含まれています。
{
let r;
{
let x = 5;
r = &x;
}
println!("r: {}", r);
}
注釈: リスト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で、このコードが動くことを許可していたら、r
はx
がスコープを抜けた時に解放されるメモリを参照していることになり、
r
で行おうとするいかなることもちゃんと動作しないでしょう。では、どうやってコンパイラはこのコードが無効であると決定しているのでしょうか?
借用チェッカーを使用しています。
借用精査機
Rustコンパイラには、スコープを比較して全ての借用が有効であるかを決定する借用チェッカーがあります。 リスト10-18は、リスト10-17と同じコードを示していますが、変数のライフタイムを表示する注釈が付いています:
{
let r; // ---------+-- 'a
// |
{ // |
let x = 5; // -+-- 'b |
r = &x; // | |
} // -+ |
// |
println!("r: {}", r); // |
} // ---------+
ここで、r
のライフタイムは'a
、x
のライフタイムは'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); // | | // --+ | } // ----------+ #}
ここでx
のライフタイムは'b
になり、今回の場合'a
よりも大きいです。つまり、
コンパイラはx
が有効な間、r
の参照も常に有効になることを把握しているので、r
はx
を参照できます。
今や、参照のライフタイムがどれだけあり、コンパイラがライフタイムを解析して参照が常に有効であることを保証する仕組みがわかったので、 関数の文脈でジェネリックな引数と戻り値のライフタイムを探究しましょう。
関数のジェネリックなライフタイム
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);
}
関数に取ってほしい引数が文字列スライス、つまり参照であることに注意してください。
何故なら、longest
関数に引数の所有権を奪ってほしくないからです。
この関数にString
のスライス(変数string1
に格納されている型)と文字列リテラル(変数string2
が含むもの)を受け取らせたいのです。
リスト10-20で使用している引数が、我々が必要としているものである理由についてもっと詳しい議論は、 第4章の「引数としての文字列スライス」節をご参照ください。
リスト10-21に示したようにlongest
関数を実装しようとしたら、コンパイルできないでしょう。
ファイル名: src/main.rs
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}
代わりに、以下のようなライフタイムに言及するエラーが出ます:
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`由来のものなのか宣言していません)
助言テキストが、戻り値の型はジェネリックなライフタイム引数である必要があると明かしています。
というのも、返している参照がx
かy
のどちらを参照しているか、コンパイラにはわからないからです。
この関数の本体のif
ブロックはx
への参照を返し、else
ブロックはy
への参照を返すので、
実際、どちらか私たちにもわかりません!
この関数を定義する際、この関数に渡される具体的な値がわからないので、if
ケースか、else
ケースが実行されるか、わからないのです。
また、渡される参照の具体的なライフタイムもわからないので、リスト10-18と10-19で、
返す参照が常に有効であるかを決定したように、スコープを見ることもできないのです。
借用チェッカーもこれを決定することはできません。x
とy
のライフタイムがどう戻り値のライフタイムと関係するかわからないからです。
このエラーを修正するには、借用チェッカーが解析を実行できるように、参照間の関係を定義するジェネリックなライフタイム引数を追加します。
ライフタイム注釈記法
ライフタイム注釈は、いかなる参照の生存期間も変えることはありません。シグニチャがジェネリックな型引数を指定していると、 関数があらゆる型を受け入れるのと全く同様に、ジェネリックなライフタイム引数を指定することで関数は、 あらゆるライフタイムの参照を受け入れるのです。ライフタイム注釈は、ライフタイムに影響することなく、 複数の参照のライフタイムのお互いの関係を記述します。
ライフタイム注釈は、少し不自然な記法です: ライフタイム引数の名前はアポストロフィー('
)で始まらなければならず、
通常全部小文字で、ジェネリック型のようにとても短いです。多くの人は、'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
という別の引数もあります。
ライフタイム注釈は、first
とsecond
の参照がどちらもジェネリックなライフタイムと同じだけ生きることを示唆します。
関数シグニチャにおけるライフタイム注釈
さて、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-20のmain
関数とともに使用したら、欲しい結果になるはずです。
これで関数シグニチャは、何らかのライフタイム'a
に対して、関数は2つの引数を取り、
どちらも少なくともライフタイム'a
と同じだけ生きる文字列スライスであるとコンパイラに教えるようになりました。
また、この関数シグニチャは、関数から返る文字列スライスも少なくともライフタイム'a
と同じだけ生きると、
コンパイラに教えています。これらの制約は、コンパイラに強制してほしいものです。
この関数シグニチャでライフタイム引数を指定する時、渡されたり、返したりした、いかなる値のライフタイムも変更していないことを思い出してください。
むしろ、借用チェッカーは、これらの制約を守らない値全てを拒否するべきと指定しています。
longest
関数は、正確にx
とy
の生存期間を知る必要はなく、何かのスコープが'a
に代替され、
このシグニチャを満足することだけ知っている必要があることに注意してください。
関数でライフタイムを注釈する際、注釈は関数シグニチャに嵌り、 関数本体には嵌りません。コンパイラは、なんの助けもなく、関数内のコードを解析できます。しかしながら、 関数に、関数外からの参照や、関数外への参照がある場合、コンパイラは引数や戻り値のライフタイムをそれだけで解決することはほとんど不可能になります。 ライフタイムは、関数が呼び出される度に異なる可能性があります。このために、手動でライフタイムを注釈する必要があるのです。
具体的な参照をlongest
に渡すと、'a
を代替する具体的なライフタイムは、y
のスコープと被さるx
のスコープの一部になります。
言い換えると、ジェネリックなライフタイム'a
は、x
とy
のライフタイムのうち、小さい方に等しい具体的なライフタイムになるのです。
返却される参照を同じライフタイム引数'a
で注釈したので、返却される参照もx
かy
のライフタイムの小さい方と同じだけ有効になるでしょう。
ライフタイム注釈が異なる具体的なライフタイムになる参照を渡すことで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); } }
この例において、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);
}
このコードのコンパイルを試みると、こんなエラーになります:
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
このエラーは、result
がprintln!
文に対して有効になるために、string2
が外側のスコープの終わりまで有効である必要があることを示しています。
関数引数と戻り値のライフタイムを同じライフタイム引数'a
で注釈したので、コンパイラはこのことを知っています。
人間からしたら、このコードを見てstring1
はstring2
よりも長いことが確認でき、
故にresult
はstring1
への参照を含んでいます。まだ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 | | }
| |_^
問題は、result
がlongest
関数の末端でスコープを抜け、片付けられてしまうことです。
また、関数から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 }; }
この構造体には文字列スライスを保持する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[..] } #}
この関数がライフタイム注釈なしでコンパイルできた理由は、歴史的なものです: 昔のバージョンの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つ入力ライフタイムがあるので、コンパイラは最初のライフタイム省略規則を適用し、
&self
とannouncement
に独自のライフタイムを与えます。それから、
引数の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
関数ですが、
ジェネリックな型T
のann
という追加の引数があり、これはwhere
節で指定されているように、
Display
トレイトを実装するあらゆる型で埋めることができます。
この追加の引数は、関数が文字列スライスの長さを比較する前に出力されるので、
Display
トレイト境界が必要なのです。ライフタイムは一種のジェネリックなので、
ライフタイム引数'a
とジェネリックな型引数T
が関数名の後、山カッコ内の同じリストに収まっています。
まとめ
いろんなことをこの章では講義しましたね!今やジェネリックな型引数、トレイトとトレイト境界、そしてジェネリックなライフタイム引数を知ったので、 多くの異なる場面で動くコードを繰り返しなく書く準備ができました。ジェネリックな型引数により、 コードを異なる型に適用させてくれます。トレイトとトレイト境界は、型がジェネリックであっても、 コードが必要とする振る舞いを持つことを保証します。ライフタイム注釈を活用して、 この柔軟なコードにダングリング参照が存在しないことを保証する方法を学びました。 さらにこの解析は全てコンパイル時に起こり、実行時のパフォーマンスには影響しません!
信じるかどうかは自由ですが、この章で議論した話題にはもっともっと学ぶべきことがあります: 第17章ではトレイトオブジェクトを議論します。これはトレイトを使用する別の手段です。 第19章では、ライフタイム注釈が関わるもっと複雑な筋書きと何か高度な型システムの機能を講義します。 ですが次は、コードがあるべき通りに動いていることを確かめられるように、Rustでテストを書く方法を学びます。