第6章: ライフタイム入門
学習目標
- ライフタイムの概念を理解する
- ライフタイム注釈の書き方を学ぶ
- ライフタイム省略規則を把握する
- 関数とライフタイムの関係を理解する
---
6.1 ライフタイムとは何か?
6.1.1 ライフタイムの必要性
ライフタイム(lifetime)は、参照が有効である範囲をコンパイラに伝えるための仕組みです。
fn main() {
let r; // ---------+-- 'a
// |
{ // |
let x = 5; // -+-- 'b |
r = &x; // | |
} // -+ |
// |
println!("{}", r); // ---------+
}
// ❌ コンパイルエラー!
// xのライフタイム('b)はrのライフタイム('a)より短い
問題点:
xは内側のスコープで破棄されるrは外側のスコープでxへの参照を保持rは無効なメモリを指す(ダングリング参照)
Rustの解決:
error[E0597]: `x` does not live long enough
コンパイラがライフタイムをチェックし、エラーを防ぎます。
6.1.2 ライフタイムの可視化
┌─────────────────────────────────────────────────────────┐
│ ライフタイムの概念図 │
├─────────────────────────────────────────────────────────┤
│ │
│ fn main() { │
│ let x = 5; // ────┐ xのライフタイム │
│ let r = &x; // ──┐ │ rのライフタイム │
│ // │ │ │
│ println!("{}", r);// ──┘ │ │
│ } // ────┘ │
│ │
│ ✅ OK: rのライフタイムはxのライフタイム内に収まっている │
│ │
└─────────────────────────────────────────────────────────┘
---
6.2 関数とライフタイム
6.2.1 問題の例
複数の参照を受け取る関数では、返り値のライフタイムが不明確です:
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 named lifetime parameter
問題:
- 返り値は
xかyのどちらかの参照 - コンパイラは返り値のライフタイムを判断できない
6.2.2 ライフタイム注釈の追加
ライフタイム注釈(lifetime annotation)で明示します:
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
// ✅ OK
構文:
'a → ライフタイムパラメータ(アポストロフィ + 名前)
<'a> → ジェネリックライフタイムの宣言
&'a str → ライフタイム'aを持つ参照
意味:
「返り値の参照は、xとyのうち短い方のライフタイムと同じ」
6.2.3 ライフタイムの動作
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!("長い方: {}", result);
} // ✅ OK: resultはこのスコープ内でのみ使用
}
分析:
string1のライフタイム:main全体string2のライフタイム:内側のスコープresultのライフタイム:短い方(string2と同じ)resultを内側のスコープで使用しているのでOK
エラーの例:
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());
} // string2が破棄される
println!("長い方: {}", result); // ❌ エラー!
}
エラー:
error[E0597]: `string2` does not live long enough
---
6.3 ライフタイム注釈の文法
6.3.1 基本的な構文
&i32 // 参照
&'a i32 // 明示的なライフタイム付き参照
&'a mut i32 // 明示的なライフタイム付き可変参照
6.3.2 複数のライフタイム
異なるライフタイムを持つ参照:
fn example<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
x
}
意味:
xはライフタイム'ayはライフタイム'b- 返り値は
'a(xと同じライフタイム)
使用例:
fn main() {
let string1 = String::from("hello");
{
let string2 = String::from("world");
let result = example(&string1, &string2);
println!("{}", result);
} // string2は破棄されるが、resultはstring1を参照しているのでOK
}
6.3.3 ライフタイムの制約
fn example<'a, 'b: 'a>(x: &'a str, y: &'b str) -> &'a str {
y
}
意味:
'b: 'a → 'bは'a以上に長く生きる
使用例:
fn main() {
let string1 = String::from("short");
let string2 = String::from("longer");
// string2のライフタイムがstring1以上であることを保証
let result = example(&string1, &string2);
}
---
6.4 構造体とライフタイム
6.4.1 参照を持つ構造体
構造体が参照を持つ場合、ライフタイム注釈が必要です:
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,
};
println!("{}", i.part);
}
ルール:
構造体のインスタンスは、参照しているデータより長く生きられない
エラーの例:
fn main() {
let i;
{
let novel = String::from("Call me Ishmael.");
let first_sentence = novel.split('.').next().unwrap();
i = ImportantExcerpt {
part: first_sentence,
};
} // novelが破棄される
// println!("{}", i.part); // ❌ エラー!
}
6.4.2 複数の参照を持つ構造体
struct Context<'a, 'b> {
prefix: &'a str,
suffix: &'b str,
}
fn main() {
let prefix = String::from("Hello");
let suffix = String::from("World");
let context = Context {
prefix: &prefix,
suffix: &suffix,
};
println!("{}, {}", context.prefix, context.suffix);
}
---
6.5 ライフタイム省略規則
6.5.1 省略規則の概要
多くの場合、ライフタイム注釈は省略できます。コンパイラが以下のライフタイム省略規則(lifetime elision rules)に従って推論します:
規則1:入力ライフタイム
各参照パラメータは独自のライフタイムを持つ
// 書いたコード
fn first_word(s: &str) -> &str
// コンパイラが推論
fn first_word<'a>(s: &'a str) -> &str
規則2:単一の入力ライフタイム
入力ライフタイムが1つだけなら、それが出力に適用される
// 書いたコード
fn first_word(s: &str) -> &str
// コンパイラが推論
fn first_word<'a>(s: &'a str) -> &'a str
規則3:&selfまたは&mut self
メソッドで、&selfがあれば、そのライフタイムが出力に適用される
// 書いたコード
impl<'a> ImportantExcerpt<'a> {
fn level(&self) -> i32 {
3
}
}
// 明示的に書くと
impl<'a> ImportantExcerpt<'a> {
fn level(&self) -> i32 {
3
}
}
6.5.2 省略できる例
// ✅ 省略可能
fn first_word(s: &str) -> &str {
&s[..1]
}
// ✅ 省略可能(規則2)
fn parse(text: &str) -> &str {
&text[..]
}
// ✅ 省略可能(規則3)
impl<'a> ImportantExcerpt<'a> {
fn announce_and_return_part(&self, announcement: &str) -> &str {
println!("Attention: {}", announcement);
self.part
}
}
6.5.3 省略できない例
// ❌ 省略不可(複数の入力ライフタイム、返り値がどちらか不明)
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() { x } else { y }
}
// ✅ 明示的に書く必要がある
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
---
6.6 'staticライフタイム
6.6.1 'staticとは
'static:プログラム全体の期間有効なライフタイム
let s: &'static str = "I have a static lifetime.";
文字列リテラル:
- プログラムのバイナリに直接埋め込まれる
- プログラムの実行中、常に有効
6.6.2 'staticの使用例
// 文字列リテラル
const MESSAGE: &'static str = "Hello, world!";
// 静的変数
static LANGUAGE: &'static str = "Rust";
fn main() {
println!("{}", MESSAGE);
println!("{}", LANGUAGE);
}
6.6.3 'staticの注意点
誤解:
「'staticを付けると全てのライフタイム問題が解決する」
現実:
'staticは本当に必要な場合のみ使用すべき
悪い例:
fn dangle() -> &'static str {
let s = String::from("hello");
// &s // ❌ これでは解決しない
"hello" // 文字列リテラルを返す
}
---
6.7 実践例
例1:文字列スライスの比較
fn longest_word<'a>(text: &'a str) -> &'a str {
text.split_whitespace()
.max_by_key(|word| word.len())
.unwrap_or("")
}
fn main() {
let sentence = String::from("The quick brown fox");
let longest = longest_word(&sentence);
println!("最長の単語: {}", longest);
}
例2:構造体メソッド
struct Excerpt<'a> {
text: &'a str,
}
impl<'a> Excerpt<'a> {
fn new(text: &'a str) -> Self {
Excerpt { text }
}
fn get_text(&self) -> &str {
self.text
}
fn first_char(&self) -> Option<char> {
self.text.chars().next()
}
}
fn main() {
let text = String::from("Hello, Rust!");
let excerpt = Excerpt::new(&text);
println!("テキスト: {}", excerpt.get_text());
if let Some(ch) = excerpt.first_char() {
println!("最初の文字: {}", ch);
}
}
例3:複数の参照を返す
fn split_at_first_space<'a>(text: &'a str) -> (&'a str, &'a str) {
match text.find(' ') {
Some(pos) => (&text[..pos], &text[pos + 1..]),
None => (text, ""),
}
}
fn main() {
let sentence = String::from("Hello World");
let (first, rest) = split_at_first_space(&sentence);
println!("最初: {}", first);
println!("残り: {}", rest);
}
---
6.8 ライフタイムのベストプラクティス
6.8.1 設計の原則
┌─────────────────────────────────────────────────────────┐
│ ライフタイム設計のガイドライン │
├─────────────────────────────────────────────────────────┤
│ │
│ 1. 所有権を優先 │
│ → 可能なら参照ではなく所有権を使う │
│ │
│ 2. ライフタイムは最小限に │
│ → 省略規則に任せる │
│ │
│ 3. 構造体の参照は避ける │
│ → データを所有する方が簡単 │
│ │
│ 4. 'staticは慎重に │
│ → 本当に必要な場合のみ │
│ │
└─────────────────────────────────────────────────────────┘
6.8.2 所有権 vs 参照
// ✅ 良い:データを所有
struct Config {
name: String,
value: String,
}
// ❌ 複雑:ライフタイム管理が必要
struct ConfigRef<'a> {
name: &'a str,
value: &'a str,
}
使い分け:
- 所有権:データの永続的な保持
- 参照:一時的な借用、パフォーマンス最適化
- ライフタイムの概念
---
6.9 まとめ
学んだこと
- ライフタイム注釈
'a、'bの構文
- 関数、構造体での使用- ライフタイム省略規則
- 'static
次のステップ
次の章では、型システムを学びます:
- プリミティブ型
- 複合型
- 型推論と型変換
- The Rust Programming Language - Chapter 10.3
- Rust By Example - Lifetimes
- Lifetime Misconceptions
---