ライフタイムを使用した有効な参照: validating references with lifetimes
Rustの全ての参照には、ライフタイムが存在する
これは、参照が有効な範囲のことである
複数の型が可能な場合は、肩にアノテーションを付ける必要がるのと同様に、 参照のライフタイムがいくつかの異なる方法で関連付けられる場合、 ライフタイムにアノテーションを付ける必要がある
Rustでは、実行時に使用される実際の参照が確実に有効になるように、 一般的なライフタイムパラメーターを使用して、関係に注釈を付ける必要がある
ライフタイムの概念は 他のプログラミング言語のツールとは多少異なり、 ほぼ間違いなくRustのもっとも特徴的な機能である
ライフタイムでのダングリング参照の防止: preventing dangling references with lifetimes
ライフタイムの主な目的は、プログラムが参照する予定のデータ以外のデータを参照する原因となる ダングリング参照を防ぐことである
{
let r;
{
let x = 5;
r = &x;
}
println!("r: {}", r);
}
- 外部スコープは初期値無しで
r
という名前の変数を宣言し、 内部スコープは5
という初期値でx
という名前の変数を宣言する - 内部スコープ内で
r
の値をx
への参照として設定しようとしてる - 内部スコープが終了し、
r
の値を出力しようとしている
このコードは、使用しようとする前にr
が参照している値が範囲外になるため、 コンパイルされずエラーとなる
ボロー(借用)チェッカー: the borrow checker
Rustコンパイラには、スコープを比較して、全ての借用が有効かどうかを判断する ボローチェッカーがある
関数の一般的なライフタイム: generic lifetimes in functions
次の処理はコンパイルエラーとなる
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
}
}
- この関数を定義するとき、この関数に渡される具体的な値はわからないため、
if
とelse
のどちらが実行されるかはわからな - 渡される参照の具体的な有効期間もわからないため、 スコープを見て返す参照が常に有効かどうかを判断することが出来ない
- ボローチェッカーは、
x
とy
おライフタイムが戻り値のライフタイムにどのように関係するのかを知らないため、 これも判断出来ない - このエラーを修正するには、参照間の関係を定義する一般的な有効期間パラメーターを追加する必要がある
ライフタイムアノテーションの構文: lifetime annotation syntax
ライフタイムアノテーションは、参照の有効期間を変更しない
シグネチャがジェネリック型を指定するときに、 関数が任意の型を受け入れることができるように、関数はジェネリックライフタイムパラメーターを指定することにより、 任意の有効期間をもつ参照を受け入れることができる
ライフタイムアノテーションは、ライフタイムに影響を与えること無く、 複数の参照のライフタイムの相互関係を記述する
ライフタイムアノテーションの構文はやや変わっている
ライフタイムパラメーターはアポストロフィ('
)で始まる必要があり、 通常はジェネリック型のように全て小文字で非常にに短くなっている
&i32 // a reference
&'a i32 // a reference with an explicit lifetime
&'a mut i32 // a mutable reference with an explicit lifetime
ライフタイムアノテーションは、それ自体ではあまり意味がない
アノテーションは、複数の参照のジェネリックライフタイムパラメーターが 互いにどのように関連するかをRustに伝えるためのものである
関数シグネチャのライフタイムアノテーション: lifetime annotation in function signatures
ジェネリック型パラメーターと同様に、 関数名とパラメーターリストの間の山括弧内にジェネリックライフタイムパラメーターを宣言する必要がある
このシグネチャで表現したい制約は、パラメーター内の全ての参照と、 戻り値の値のライフタイムが同じでなければならないというこどである
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
関数シグネチャは、Rustにあるライフタイム'a
に対して、 少なくともライフタイム'a
と同じ長さの文字列スライスである2つのパラメーターを取ることを伝える
また、関数シグネチャは関数から返された文字列スライスが少なくともライフタイム'a
の間存続することを Rustに伝える
この関数シグネチャでライフタイムパラメーターを指定する場合、 渡される値または返される値のライフタイムを変更しないことに注意
関数のライフタイムにアノテーションを付ける場合、 アノテーションは関数の本体にではなく、関数のシグネチャに入れられる
関数がその関数の外部のコードへの参照またはそのコードからの参照を持っている場合、 Rustがそれ自体でパラメーターの寿命や戻り値を把握することはほとんど不可能になる
ライフタイムは、関数が呼び出されるたびに異なる場合がある
これが、ライフタイムに手動でアノテーションを付ける必要がある理由である
ライフタイムの観点から考える: thinking in terms of lifetimes
ライフタイムパラメーターを指定する必要があるかは、 関数が何をしているかによって異なる
例えば、x
とy
のうち常にx
をを返すような場合、 y
にライフタイムを指定する必要はない
fn longest<'a>(x: &'a str, y: &str) -> &'a str {
x
}
関数から参照を返す場合、戻り値の型のライフタイムは、 いずれかの引数のライフタイムと一致する必要がある
返された参照が引数のいずれかを参照しない場合、 この関数内で作成された値を参照する必要があるが、 これはダングリング参照になる
fn longest<'a>(x: &str, y: &str) -> &'a str {
let result = String::from("really long string");
result.as_str()
}
この場合の最良の解決策は、 参照ではなく、所有データ型を返すことである
構造体定義のライフタイムアノテーション: lifetime annotations in struct definitions
構造体が参照を保持する場合、 構造体の定義内の全ての参照にライフタイムアノテーションを追加する必要がある
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 };
}
ジェネリックデータ型と同様に、 構造体の名前の後に山括弧で囲んだジェネリックライフタイムパラメーターの名前を宣言し、 構造体定義の本文でライフタイムパラメーターを使用できるようにしている
このアノテーションは、ImportantExcerpt
のインスタンスが、 そのpart
フィールドに保持している参照より長く存続出来ないことを意味している novel
はImportantExcerpt
インスタンスが作成される前に存在し、 ImportantExcerpt
が範囲外なるまえ、novel
も範囲外にならない
ライフタイムエリシオン: lifetime elision
全ての参照にはライフタイムがあるが、 次の関数はコンパイルされている
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の初期バージョンでは、全ての参照に明示的なライフタイムが必要だったため、 このコードはコンパイルされていなかった
fn first_word<'a>(s: &'a str) -> &'a str {
多くのRustコードを書いた後、RustチームはRustプログラマーが特定の状況で何度も同じ ライフタイムアノテーションを挿入していうることを発見した
これらの状況は予測可能であり、いくつかの確定的なパターンが存在した
開発者はこれらのパターンをコンパイラのコードににプログラムしたため、 ボローチェッカーはこれらの状況でのライフタイムを推測でき、 明示的な注釈は必要ない
将来的には、必要なライフタイムアノテーションが更に少なくなる可能性があ
Rustの参照分析にプログラムされたパターンは、ライフタイム除外(エリシオン)ルールと呼ばれる
これらは、プログラマーが従うべき規則ではなく、 コンパイラが考慮する特定のケースのセットであり、 コードがこれらのケースに適合する場合、 ライフタイムを明示的に記述する必要はない
省略(エリシオン)ルールは完全な推論を提供しない
Rustが規則を決定論的に適用しているが、 参照の有効期間が曖昧な場合、 コンパイラは残りの参照の有効期間を推測しない
この場合、コンパイラは推測する代わりにエラーを表示する
エラーは、参照の相互関係を指定するライフタイムアノテーションを追加することで 解決できる
関数のメソッドのパラメーターのライフタイムはインプットライフタイムと呼ばれ、 戻り値のライフタイムはアウトプットライフタイムと呼ばれる
コンパイラは、3つのルールを使用し、 明示的なアノテーションがない場合の有効期間を把握する
最初のルールはインプットライフタイムに適用され、 2,3番目のルールはアウトプットライフタイムに適用される
コンパイラが3つのルールの最後に到達しても、 ライフタイムを把握出来ない参照がまだある場合、コンパイラはエラーで停止する
これらのルールは、 fn
定義とimpl
ブロックに適用される
最初のルールは、参照である各パラメーターが独自のライフタイムパラメーターを取得することである
2番目のルールは、 インプットライフタイムパラメーターが1つだけの場合、 そのライフタイムパラメーターは全ての アウトプットライフタイムパラメーターに割り当てられる
3番目のルールは、 複数のインプットライフタイムパラメーターのうち、 1つが&self
または&mut self
である場合、 これはメソッドであるため、self
内のライフタイムは全て アウトプットライフタイムパラメーターに割り当てられる
この3番目の規則により、必要なシンボルの数がすくなくなるため、 メソッドの読み書きがより便利になる
メソッド定義のライフタイムアノテーション
ライフタイムを持つ構造体にメソッドを実装する場合、 ジェネリック型パラメーターの構文と同じ構文を使用する
ライフタイムパラメーターを宣言して使用する場所は、 それらが構造体フィールドまたはメソッドパラメータと戻り値のどちらに関連するかによって異なる
構造体フィールドのライフタイム名は常にimpl
キーワードの後に宣言し、 構造体の名前の後に使用する必要がある
impl
ブロック内のメソッドシグネチャでは、 参照は構造体のフィールド内の参照のライフタイムに関連付けられているか、 独立している場合がある
さらに、ライフタイム除外ルールにより、メソッドシグネチャでライフタイムアノテーションが 不要になることがよくある
impl<'a> ImportantExcerpt<'a> {
fn announce_and_return_part(&self, announcement: &str) -> &str {
println!("Attention please: {}", announcement);
self.part
}
}
インプットライフタイムは2つあるため、 Rustは最初のライフタイム省略ルールを適用し、 &self
とannouncement
の両方に独自のライフタイムを与える
また、パラメータの2つが&self
であるため、 戻り値の型は&self
の有効期間を取得し、 全ての有効期間が考慮されている
静的ライフタイム: the static lifetime
議論する必要がある特別なライフタイムの1つは、静的(static
)である
この参照は、プログラムの全期間にわたって存続できる
全ての文字列リテラルには、静的ライフタイムがあり、 次のように注釈を付けることができる
let s: &'static str = "I have a static lifetime.";
この文字列のテキストは、 プログラムのバイナリに直接保存され、常に利用可能である
したがって、 全ての文字列リテラルは静的である
ジェネリック型パラメーター、トレイとバインド、ライフタイム: generic type parameters, trait bounds, and lifetimes together
今までのおさらい
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
}
}