ライフタイムで参照を検証する
第4章の「参照と借用」節で議論しなかった詳細の一つに、Rustにおいて参照は全てライフタイムを保持するということがあります。 ライフタイムとは、その参照が有効になるスコープのことです。多くの場合、型が推論されるように、 大体の場合、ライフタイムも暗黙的に推論されます。複数の型の可能性があるときには、型を注釈しなければなりません。 同様に、参照のライフタイムがいくつか異なる方法で関係することがある場合には注釈しなければなりません。 コンパイラは、ジェネリックライフタイム引数を使用して関係を注釈し、実行時に実際の参照が確かに有効であることを保証することを要求するのです。
ライフタイムの概念は、他のプログラミング言語の道具とはどこか異なり、間違いなくRustで一番際立った機能になっています。 この章では、ライフタイムの全体を解説することはしませんが、 ライフタイム記法が必要となる最も一般的な場合について議論しますので、ライフタイムの概念について馴染むことができるでしょう。
ライフタイムでダングリング参照を回避する
ライフタイムの主な目的は、ダングリング参照を回避することです。ダングリング参照によりプログラムは、 参照するつもりだったデータ以外のデータを参照してしまいます。リスト10-17のプログラムを考えてください。 これには、外側のスコープと内側のスコープが含まれています。
fn main() {
{
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
が参照している値が使おうとする前にスコープを抜けるので、
このコードはコンパイルできません。こちらがエラーメッセージです:
$ cargo run
Compiling chapter10 v0.1.0 (file:///projects/chapter10)
error[E0597]: `x` does not live long enough
(エラー[E0597]: `x`の生存期間が短すぎます)
--> src/main.rs:7:17
|
7 | r = &x;
| ^^ borrowed value does not live long enough
| (借用された値の生存期間が短すぎます)
8 | }
| - `x` dropped here while still borrowed
| (`x`は借用されている間にここでドロップされました)
9 |
10 | println!("r: {}", r);
| - borrow later used here
| (その後、借用はここで使われています)
error: aborting due to previous error
For more information about this error, try `rustc --explain E0597`.
error: could not compile `chapter10`.
To learn more, run the command again with --verbose.
変数x
の「生存期間が短すぎます」。原因は、内側のスコープが7行目で終わった時点でx
がスコープを抜けるからです。
ですが、r
はまだ、外側のスコープに対して有効です; スコープが大きいので、「長生きする」と言います。
Rustで、このコードが動くことを許可していたら、r
はx
がスコープを抜けた時に解放されるメモリを参照していることになり、
r
で行おうとするいかなることもちゃんと動作しないでしょう。では、どうやってコンパイラはこのコードが無効であると決定しているのでしょうか?
それは、借用チェッカーを使用しているのです。
借用精査機
Rustコンパイラには、スコープを比較して全ての借用が有効であるかを決定する借用チェッカーがあります。 リスト10-18は、リスト10-17と同じコードを示していますが、変数のライフタイムを表示する注釈が付いています。
fn main() {
{
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でコードを修正したので、ダングリング参照はなくなり、エラーなくコンパイルできます。
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 longest 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
関数に引数の所有権を奪ってほしくないからです。
リスト10-20で使用している引数が、我々が必要としているものである理由についてもっと詳しい議論は、
第4章の「引数としての文字列スライス」節をご参照ください。
リスト10-21に示すようにlongest
関数を実装しようとしたら、コンパイルできないでしょう。
ファイル名: 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);
}
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}
代わりに、以下のようなライフタイムに言及するエラーが出ます:
$ cargo run
Compiling chapter10 v0.1.0 (file:///projects/chapter10)
error[E0106]: missing lifetime specifier
(エラー[E0106]: ライフタイム指定子が不足しています)
--> src/main.rs:9:33
|
9 | 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`どちらから借用されたものなのか宣言していません)
error: aborting due to previous error
For more information about this error, try `rustc --explain E0106`.
error: could not compile `chapter10`.
To learn more, run the command again with --verbose.
助言テキストが、戻り値の型はジェネリックなライフタイム引数である必要があると明かしています。
というのも、返している参照が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
関数を例にライフタイム注釈を詳しく見ていきましょう。ジェネリックな型引数同様、
関数名と引数リストの間の山カッコの中にジェネリックなライフタイム引数を宣言します。
このシグニチャで表現したい制約は、引数の全ての参照と戻り値が同じライフタイムを持つことです。
リスト10-22に示すように、ライフタイムを'a
と名付け、それを各参照に付与します。
ファイル名: 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); } 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
関数が返す参照のライフタイムは、渡された参照のうち、小さい方のライフタイムと同じであるという事です。
これらの制約は、まさに私たちがコンパイラに保証してほしかったものです。
この関数シグニチャでライフタイム引数を指定する時、渡されたり、返したりした、いかなる値のライフタイムも変更していないことを思い出してください。
むしろ、借用チェッカーは、これらの制約を守らない値全てを拒否するべきと指定しています。
longest
関数は、x
とy
の正確な生存期間を知っている必要はなく、
このシグニチャを満たすようなスコープを'a
に代入できることを知っているだけであることに注意してください。
関数にライフタイムを注釈するときは、注釈は関数の本体ではなくシグニチャに付与します。 コンパイラは注釈がなくとも関数内のコードを解析できます。しかしながら、 関数に関数外からの参照や関数外への参照がある場合、コンパイラが引数や戻り値のライフタイムを自力で解決することはほとんど不可能になります。 そのライフタイムは、関数が呼び出される度に異なる可能性があります。このために、手動でライフタイムを注釈する必要があるのです。
具体的な参照をlongest
に渡すと、'a
に代入される具体的なライフタイムは、x
のスコープの一部であってy
のスコープと重なる部分となります。
言い換えると、ジェネリックなライフタイム'a
は、x
とy
のライフタイムのうち、小さい方に等しい具体的なライフタイムになるのです。
返却される参照を同じライフタイム引数'a
で注釈したので、返却される参照もx
かy
のライフタイムの小さい方と同じだけ有効になるでしょう。
ライフタイム注釈が異なる具体的なライフタイムを持つ参照を渡すことでlongest
関数を制限する方法を見ましょう。
リスト10-23はそのシンプルな例です。
ファイル名: src/main.rs
fn main() { // 長い文字列は長い let string1 = String::from("long string is long"); // (訳注:この言葉自体に深い意味はない。下の"xyz"より長いということだけが重要) { let string2 = String::from("xyz"); let result = longest(string1.as_str(), string2.as_str()); // 一番長い文字列は{} println!("The longest string is {}", result); } } fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } }
この例において、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);
}
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
このコードのコンパイルを試みると、こんなエラーになります:
$ cargo run
Compiling chapter10 v0.1.0 (file:///projects/chapter10)
error[E0597]: `string2` does not live long enough
--> src/main.rs:6:44
|
6 | result = longest(string1.as_str(), string2.as_str());
| ^^^^^^^ borrowed value does not live long enough
7 | }
| - `string2` dropped here while still borrowed
8 | println!("The longest string is {}", result);
| ------ borrow later used here
error: aborting due to previous error
For more information about this error, try `rustc --explain E0597`.
error: could not compile `chapter10`.
To learn more, run the command again with --verbose.
このエラーは、result
がprintln!
文に対して有効であるためには、string2
が外側のスコープの終わりまで有効である必要があることを示しています。
関数引数と戻り値のライフタイムを同じライフタイム引数'a
で注釈したので、コンパイラはこのことを知っています。
人間からしたら、string1
はstring2
よりも長く、それ故にresult
がstring1
への参照を含んでいることは
コードから明らかです。まだstring1
はスコープを抜けていないので、
string1
への参照はprintln!
にとって有効でしょう。ですが、コンパイラはこの場合、
参照が有効であると見なせません。longest
関数から返ってくる参照のライフタイムは、
渡した参照のうちの小さい方と同じだとコンパイラに指示しました。したがって、
借用チェッカーは、リスト10-24のコードを無効な参照がある可能性があるとして許可しないのです。
試しに、値や、longest
関数に渡される参照のライフタイムや、返される参照の使われかたが異なる実験をもっとしてみてください。
コンパイル前に、その実験が借用チェッカーを通るかどうか仮説を立ててください; そして、正しいか確かめてください!
ライフタイムの観点で思考する
何にライフタイム引数を指定する必要があるかは、関数が行っていることに依存します。例えば、
longest
関数の実装を最長の文字列スライスではなく、常に最初の引数を返すように変更したら、
y
引数に対してライフタイムを指定する必要はなくなるでしょう。以下のコードはコンパイルできます:
ファイル名: src/main.rs
fn main() { let string1 = String::from("abcd"); let string2 = "efghijklmnopqrstuvwxyz"; let result = longest(string1.as_str(), string2); println!("The longest string is {}", result); } fn longest<'a>(x: &'a str, y: &str) -> &'a str { x }
この例では、引数x
と戻り値に対してライフタイム引数'a
を指定しましたが、引数y
には指定していません。
y
のライフタイムはx
や戻り値のライフタイムとは何の関係もないからです。
関数から参照を返す際、戻り値型のライフタイム引数は、引数のうちどれかのライフタイム引数と一致する必要があります。
返される参照が引数のどれかを参照していないならば、この関数内で生成された値を参照しているはずです。
すると、その値は関数の末端でスコープを抜けるので、これはダングリング参照になるでしょう。
以下に示す、コンパイルできないlongest
関数の未完成の実装を考えてください:
ファイル名: 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);
}
fn longest<'a>(x: &str, y: &str) -> &'a str {
// 本当に長い文字列
let result = String::from("really long string");
result.as_str()
}
ここでは、たとえ、戻り値型にライフタイム引数'a
を指定していても、戻り値のライフタイムは、
引数のライフタイムと全く関係がないので、この実装はコンパイルできないでしょう。
こちらが、得られるエラーメッセージです:
$ cargo run
Compiling chapter10 v0.1.0 (file:///projects/chapter10)
error[E0515]: cannot return value referencing local variable `result`
(エラー[E0515]: ローカル変数`result`を参照している値は返せません)
--> src/main.rs:11:5
|
11 | result.as_str()
| ------^^^^^^^^^
| |
| returns a value referencing data owned by the current function
| `result` is borrowed here
| (現在の関数に所有されているデータを参照する値を返しています
| `result`はここで借用されています)
error: aborting due to previous error
For more information about this error, try `rustc --explain E0515`.
error: could not compile `chapter10`.
To learn more, run the command again with --verbose.
問題は、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
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[..] } fn main() { let my_string = String::from("hello world"); // first_word works on slices of `String`s let word = first_word(&my_string[..]); let my_string_literal = "hello world"; // first_word works on slices of string literals let word = first_word(&my_string_literal[..]); // Because string literals *are* string slices already, // this works too, without the slice syntax! let word = first_word(my_string_literal); }
この関数がライフタイム注釈なしでコンパイルできる理由には、Rustの歴史が関わっています: 昔のバージョンのRust(1.0以前)では、 全参照に明示的なライフタイムが必要だったので、このコードはコンパイルできませんでした。 その頃、関数シグニチャはこのように記述されていたのです:
fn first_word<'a>(s: &'a str) -> &'a str {
多くのRustコードを書いた後、Rustチームは、Rustプログラマが、 特定の場面で何度も同じライフタイム注釈を入力していることを発見しました。これらの場面は予測可能で、 いくつかの決まりきったパターンに従っていました。開発者はこのパターンをコンパイラのコードに落とし込んだので、 このような場面には借用チェッカーがライフタイムを推論できるようになり、明示的な注釈を必要としなくなったのです。
ここで、このRustの歴史話が関係しているのは、他にも決まりきったパターンが出現し、コンパイラに追加されることもあり得るからです。 将来的に、さらに少数のライフタイム注釈しか必要にならない可能性もあります。
コンパイラの参照解析に落とし込まれたパターンは、ライフタイム省略規則と呼ばれます。 これらはプログラマが従う規則ではありません; コンパイラが考慮する一連の特定のケースであり、 自分のコードがこのケースに当てはまれば、ライフタイムを明示的に書く必要はなくなります。
省略規則は、完全な推論を提供しません。コンパイラが決定的に規則を適用できるけれども、 参照が保持するライフタイムに関してそれでも曖昧性があるなら、コンパイラは、残りの参照がなるべきライフタイムを推測しません。 この場合コンパイラは、それらを推測するのではなくエラーを与えます。 これらは、参照がお互いにどう関係するかを指定するライフタイム注釈を追記することで解決できます。
関数やメソッドの引数のライフタイムは、入力ライフタイムと呼ばれ、 戻り値のライフタイムは出力ライフタイムと称されます。
コンパイラは3つの規則を活用し、明示的な注釈がない時に、参照がどんなライフタイムになるかを計算します。
最初の規則は入力ライフタイムに適用され、2番目と3番目の規則は出力ライフタイムに適用されます。
コンパイラが3つの規則の最後まで到達し、それでもライフタイムを割り出せない参照があったら、
コンパイラはエラーで停止します。
これらのルールはfn
の定義にもimpl
ブロックにも適用されます。
最初の規則は、参照である各引数は、独自のライフタイム引数を得るというものです。換言すれば、
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
というメソッドを使用します:
struct ImportantExcerpt<'a> { part: &'a str, } impl<'a> ImportantExcerpt<'a> { fn level(&self) -> i32 { 3 } } impl<'a> ImportantExcerpt<'a> { fn announce_and_return_part(&self, announcement: &str) -> &str { // "お知らせします: {}" println!("Attention please: {}", announcement); self.part } } 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, }; }
impl
後のライフタイム引数宣言と型名の後にそれを使用するのは必須ですが、最初の省略規則のため、
self
への参照のライフタイムを注釈する必要はありません。
3番目のライフタイム省略規則が適用される例はこちらです:
struct ImportantExcerpt<'a> { part: &'a str, } impl<'a> ImportantExcerpt<'a> { fn level(&self) -> i32 { 3 } } impl<'a> ImportantExcerpt<'a> { fn announce_and_return_part(&self, announcement: &str) -> &str { // "お知らせします: {}" println!("Attention please: {}", announcement); self.part } } 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, }; }
2つ入力ライフタイムがあるので、コンパイラは最初のライフタイム省略規則を適用し、
&self
とannouncement
に独自のライフタイムを与えます。それから、
引数の1つが&self
なので、戻り値型は&self
のライフタイムを得て、
全てのライフタイムが説明されました。
静的ライフタイム
議論する必要のある1種の特殊なライフタイムが、'static
であり、これは、この参照がプログラムの全期間生存できる事を意味します。
文字列リテラルは全て'static
ライフタイムになり、次のように注釈できます:
#![allow(unused)] fn main() { // 僕は静的ライフタイムを持ってるよ let s: &'static str = "I have a static lifetime."; }
この文字列のテキストは、プログラムのバイナリに直接格納され、常に利用可能です。故に、全文字列リテラルのライフタイムは、
'static
なのです。
エラーメッセージで、'static
ライフタイムを使用するよう勧める提言を見かける可能性があります。
ですが、参照に対してライフタイムとして'static
を指定する前に、今ある参照が本当にプログラムの全期間生きるかどうか考えてください。
それが可能であったとしても、参照がそれだけの期間生きてほしいのかどうか考慮するのも良いでしょう。
ほとんどの場合、問題は、ダングリング参照を生成しようとしているか、利用可能なライフタイムの不一致が原因です。
そのような場合、解決策はその問題を修正することであり、'static
ライフタイムを指定することではありません。
ジェネリックな型引数、トレイト境界、ライフタイムを一度に
ジェネリックな型引数、トレイト境界、ライフタイム指定の構文のすべてを1つの関数で簡単に見てみましょう!
fn main() { let string1 = String::from("abcd"); let string2 = "xyz"; let result = longest_with_an_announcement( string1.as_str(), string2, "Today is someone's birthday!", ); println!("The longest string is {}", result); } 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章ではトレイトオブジェクトを議論します。これはトレイトを使用する別の手段です。 非常に高度な筋書きの場合でのみ必要になる、ライフタイム注釈が関わる、もっと複雑な筋書きもあります。 それらについては、Rust Referenceをお読みください。 ですが次は、コードがあるべき通りに動いていることを確かめられるように、Rustでテストを書く方法を学びます。