ライフタイム('a)
| 対応: | Rust 1.0(2015) |
|---|
ライフタイム(lifetime)は参照の有効期間を示すアノテーションで、コンパイラがダングリング参照を検出するために使用します。関数や構造体で複数の参照の有効期間を関連付けるときに明示的に記述します。
構文
// 関数シグネチャでのライフタイムアノテーション
fn 関数名<'a>(x: &'a str, y: &'a str) -> &'a str { ... }
// 構造体フィールドのライフタイム
struct 構造体名<'a> {
field: &'a str,
}
// 静的ライフタイム(プログラム全体で有効)
let s: &'static str = "hello";
ライフタイム記法一覧
| 記法 | 概要 |
|---|---|
| 'a | ライフタイムパラメータです。任意の名前を使えますが慣習的に'aから始めます。 |
| &'a T | ライフタイム'aを持つ型Tへの不変参照です。 |
| &'a mut T | ライフタイム'aを持つ型Tへの可変参照です。 |
| 'static | プログラムの実行中ずっと有効な特別なライフタイムです。文字列リテラルが該当します。 |
サンプルコード
lifetime.rs
// 2つの文字列スライスのうち長い方を返す
// 戻り値は引数と同じライフタイムを持つ
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
// ライフタイムを持つ構造体
struct Important<'a> {
content: &'a str,
}
impl<'a> Important<'a> {
fn display(&self) {
println!("{}", self.content);
}
}
fn main() {
let s1 = String::from("long string");
let result;
{
let s2 = String::from("xyz");
result = longest(s1.as_str(), s2.as_str());
println!("最長: {}", result); // s2が有効な間だけresultを使えます。
}
// 静的ライフタイム
let s: &'static str = "プログラム終了まで有効です";
println!("{}", s);
let novel = String::from("物語が始まります。次の章では...");
let first_sentence;
{
let i = Important {
content: novel.split('。').next().unwrap(),
};
i.display();
first_sentence = i.content;
}
println!("{}", first_sentence);
}
rustc lifetime.rs ./lifetime 最長: long string プログラム終了まで有効です 物語が始まります 物語が始まります
借用(borrowing)との関係
ライフタイムは「借用チェッカー(borrow checker)」と密接に関わっています。借用チェッカーはすべての参照が有効なデータを指していることをコンパイル時に保証します。ライフタイムアノテーションは参照の有効期間を延ばすものではなく、複数の参照の「長さの関係」をコンパイラに伝えるだけです。
sample_lifetime_borrow.rs
// 借用とライフタイムの基本的な関係
fn main() {
// s1 の方が s2 より長く生きます
let s1 = String::from("Kiryu Kazuma");
{
let s2 = String::from("Majima Goro");
// longest関数は戻り値を短い方のライフタイムに合わせます
let result = longest(s1.as_str(), s2.as_str());
println!("最長: {}", result); // s2 が有効なうちなので OK
}
// println!("{}", result); // NG: s2 は既に無効(result は s2 を参照する可能性がある)
// 不変参照と可変参照は同時に持てません(借用規則)
let mut data = vec![1, 2, 3];
let first = &data[0]; // 不変借用
// data.push(4); // コンパイルエラー: 不変借用中に可変操作はできません
println!("first: {}", first); // ここまで first の不変借用が有効
data.push(4); // first の借用が終わったので OK
println!("data: {:?}", data);
}
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
rustc sample_lifetime_borrow.rs ./sample_lifetime_borrow 最長: Kiryu Kazuma first: 1 data: [1, 2, 3, 4]
ライフタイム省略規則
Rustコンパイラは多くの場合ライフタイムを自動推論します(ライフタイム省略規則 / lifetime elision rules)。省略規則で推論できない場合のみ、明示的なアノテーションが必要です。省略規則を知ることで「なぜここだけ書かなくていいのか」が理解できます。
| 規則 | 内容 |
|---|---|
| 規則1 | 各入力参照パラメータには独立したライフタイムが付きます(&str は &'a str に) |
| 規則2 | 入力ライフタイムが1つだけなら、出力ライフタイムもそれと同じになります |
| 規則3 | メソッドの場合、&self または &mut self があれば出力ライフタイムは self と同じになります |
sample_lifetime_elision.rs
// 省略できる例(規則2: 入力が1つ → 出力は同じライフタイム)
fn first_word(s: &str) -> &str { // 省略形
// fn first_word<'a>(s: &'a str) -> &'a str と同じ意味です
s.split_whitespace().next().unwrap_or("")
}
// 省略できない例(入力が複数で出力がある → どちらのライフタイムかコンパイラが判断できない)
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
// メソッドの省略(規則3: &self があれば出力は self と同じ)
struct Excerpt<'a> {
text: &'a str,
}
impl<'a> Excerpt<'a> {
fn get_text(&self) -> &str { // &'a str を省略できます
self.text
}
}
fn main() {
let sentence = String::from("Yakuza: Like a Dragon is great.");
let word = first_word(&sentence);
println!("最初の単語: {}", word);
let e = Excerpt { text: &sentence };
println!("テキスト: {}", e.get_text());
}
rustc sample_lifetime_elision.rs ./sample_lifetime_elision 最初の単語: Yakuza: テキスト: Yakuza: Like a Dragon is great.
よくあるミス1: ダングリング参照
関数内のローカル変数への参照を返そうとするとコンパイルエラーになります。
// fn dangling() -> &str {
// let s = String::from("hello");
// &s // コンパイルエラー: s はこの関数のスコープでのみ有効です
// }
修正: String を返す(所有権を移す)かパラメータの参照を返します。
fn no_dangling() -> String {
String::from("hello")
}
よくあるミス2: 構造体のライフタイム書き忘れ
構造体に参照を持たせてライフタイムを書き忘れるとコンパイルエラーになります。
// struct Config {
// name: &str, // コンパイルエラー: ライフタイムが必要です
// }
修正: ライフタイムを明示します。
sample_lifetime_mistakes.rs
struct Config<'a> {
name: &'a str,
}
fn main() {
let s = no_dangling();
println!("{}", s);
let name = String::from("Kiryu");
let config = Config { name: &name }; // Config の生存期間は name 以内に制限されます
println!("Config: {}", config.name);
}
rustc sample_lifetime_mistakes.rs ./sample_lifetime_mistakes hello Config: Kiryu
概要
ライフタイムアノテーションは参照の有効期間を変えるものではなく、コンパイラに複数の参照の有効期間の関係を伝えるためのものです。多くの場合、コンパイラが「ライフタイム省略規則(elision rules)」により自動推論するため、明示的な記述が不要なこともあります。関数の引数と戻り値に参照が含まれ、省略規則でコンパイラが推論できない場合にライフタイム注釈が必要になります。
ライフタイムの不一致はコンパイルエラーになります。関数が参照を返す場合、戻り値のライフタイムはいずれかの引数のライフタイムと関連付ける必要があります。
参照の基本については借用 / 参照(&)を参照してください。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。