Rustのライフタイムは、参照の有効範囲を指定するもので、コンパイラがポインタが無効化されていないこと(ダングリング参照がないこと)を確認できるようにします。これにより、ガベージコレクタなしでコンパイル時にメモリの安全性を保証することができます。
参照を扱うとき、Rustはコンパイラがそれを推測できない場合に、明示的にライフタイムを指定することを求めます。通常は、'aという構文を使って行います:
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } }
ここでは、両方の引数と返り値が同じライフタイムを持っており、返される参照は引数のいずれかより長く生存しないことを保証します。
ライフタイムはデータのライフタイムを変更するものではなく、コンパイラにそれを説明するだけです。
関数内のローカル変数への参照を返すことはできますか?
いいえ、できません: その変数は関数から出ると破棄されるためです。例:
fn foo() -> &String { // コンパイルエラー! let s = String::from("hello"); &s } // sへの参照は無効になります
コンパイラはそのようなコードのコンパイルを許可しません:破棄されたデータへの参照の使用からあなたを守るためです。
物語
チームにメモリリークが多発したのは、関数がローカルバッファへの参照を誤って返してしまったからです。これがうまくいかず、コンパイラがライフタイムについて警告を発することで救われました。このようなエラーが頻発したため、関数がネストされた参照を持つ複雑な構造体を扱う場合はライフタイムを明示的に指定するというルールが決められました。
物語
プロジェクトでは、データをキャッシュするためにジェネリックコードが書かれました。ライフタイムのジェネリックパラメータの設計が不適切だったため、「ライフタイムを推測できない」というエラーが発生し、キャッシュ内に保存されているデータのライフタイムを推測できなくなりました。これにより、ライフタイム注釈を試行錯誤で設定することになり、キャッシュされたデータと非キャッシュデータを異なる構造体に分けるという解決策が取られました。
物語
同僚の一人が接続プールを実装しようとしましたが、接続のライフタイムがプールのライフタイムと一致していないことを考慮していませんでした。その結果、接続が解放された後にダングリング参照が発生し、統合テストの段階で初めて気づきました。その後、プロジェクトは安全なラッパー(Arc<Mutex<T>>)に移行しました。