Rustのライフタイム注釈は「いつ」「なぜ」必要なのか

# Rust入門者とライフタイム注釈との出会い
多くのRust入門書で、ライフタイム注釈の説明が出てくるのはかなり後半です。なので、Rust入門者とライフタイム注釈との出会いは往々にして実際にコードを書いている際、ということになりがちです。「コンパイラが急に謎のアポストロフィを要求してきて、言われた通りに直したら動いたけど、一体何をしたのか分からないし、コードが禍々しいアポストロフィだらけになってしまった……。」あたりが典型例でしょうか。

# この記事の対象読者
必要なことは全て[Rust公式チュートリアルの10.3章](https://doc.rust-jp.rs/book-ja/ch10-03-lifetime-syntax.html)に書いてありますので、まずはそちらを参照して下さい。その上で、

- 読んだが理解できない
- なぜこんな仕組みが必要なのかモヤモヤする（**なぜ**必要なのか分からない）
- コンパイラに指摘される前に気付ける気がしない（**いつ**必要なのか分からない）

としたら、この記事がお役に立てるかもしれません。

# なぜ注釈が必要なのか
ライフタイム注釈がなぜ必要なのか。それを実感するために、架空のプログラミング言語**Lust**（Lifetime annotation-less rUST）を使って思考実験をしてみましょう。Lustはライフタイム注釈に関する文法がすっぽり抜け落ちている以外は、完全にRustと同一の文法を持つ言語とします。

例えばLustで以下のシンプルな２つの関数、`func1`と`func2`を定義してみましょう。どちらも2つの文字列参照を受け取り、printして、片側の引数だけを返り値として返すの関数です。`func1`と`func2`の違いは、第一引数と第二引数のどちらを返すか、だけです。
```rust,ignore
// 2つの文字列参照を受け取り、printして、第一引数だけを返す関数
fn func1(x: &str, y: &str) -> &str {
    println!("{x} {y}");
    x
}

// 2つの文字列参照を受け取り、printして、第二引数だけを返す関数
fn func2(x: &str, y: &str) -> &str {
    println!("{x} {y}");
    y
}
```
そして、main関数から`func1`と`func2`を以下のように呼んでみます。どうなるでしょうか。
```rust
fn main() {
    let s1: &str;
    let s2: &str;
    let hello = "Hello".to_string();
    {
        let world = "World!".to_string();
        s1 = func1(&hello, &world);      // <-- (1)
        s2 = func2(&hello, &world);      // <-- (2)
    }                                    // <-- (3)
    println!("{}", s1);                  // <-- (4)
    println!("{}", s2);                  // <-- (5)
}

 ```
`func1`の方は問題ないでしょう。(1)の箇所で`Hello World!`がprintされたあとに`s1`へと`hello`の参照が代入され、(4)の箇所で再度`Hello`とprintされるはずです。

`func2`の方は問題があります。(2)の箇所で`Hello`と`World!`がprintされたあと`s2`へと`world`への参照が代入されますが、(3)の箇所で`world`の実体はスコープから外れ、破棄されてしまいます。にもかかわらず(5)で`world`の実体を参照する`s2`を使おうとするため、コンパイルエラーになるでしょう。

これはまずい事態でしょうか？「別に問題ないのでは」と思う人もいるかもしれません。「確かにこの例での`func2`の使われ方には問題がある。でも(5)の部分で問題が生じるのは**明白**なのだからコンパイルエラーが出るはずだし、それを見てコードを修正すれば良いじゃないか。ライフタイム注釈なんてものが出る幕は無い。」と。

いや、コンパイラはそれで良いかもしれませんが[^2]、我々人類にとってはそうとも言えないのです。エラーの原因を考えてみてください。(4)と(5)の箇所でのエラーの有無を生んだのは、`func1`と`func2`がそれぞれ第一引数か第二引数のどちらを返すか、でした。つまり**関数内部の事情**です。

これの意味するところは、あなたは**この問題に対処するために関数内部のコードを読まなければならない**、ということです。このサンプルくらい単純なら良いですが、もし他人が書いた複雑な関数だったらどうでしょうか。ある日crates.ioで見つけただけのライブラリでこんなエラーが起きたら？

Lustの世界にもdocs.lsがあるとして、`func1`と`func2`の定義はきっと以下のように記されているでしょう。両者の関数シグネチャは完全に同じで、挙動の違いを使用者が予見することは不可能です。
```rust,ignore
// Lustの場合
fn func1(x: &str, y: &str) -> &str
fn func2(x: &str, y: &str) -> &str
```

そもそも静的な型システムとはなんのためにあるんでしたっけ。答えは複数あるでしょうが、最大のメリットの一つが「**安全性**」です。`fn(x: &str) -> String {}`という関数があったとして、引数xとして&str型を与えてString型を受け取るのなら（実行時panicやロジックの正誤はともかく）プログラムそのものは正常にコンパイルされることが保証されている。これが静的型の良い所だったはずです。型情報自体が関数の仕様書、あるいは関数の実装者と使用者の契約書と言っても良いでしょう。

関数`func2`において、使用者は契約どおりに&strを投げて&strを受けとったのに、そのせいでコンパイルエラーが返ってきたのです。Lustの型システムは崩壊しています。

対してRustではどうでしょうか？関数の定義は以下のようになります。ライフタイム注釈の登場です。
```rust,ignore
// Rustの場合
fn func1<'a,'b>(x: &'a str, y: &'b str) -> &'a str
fn func2<'a,'b>(x: &'a str, y: &'b str) -> &'b str
```

注釈記法の具体的な読み方は次章で説明しますが、まずは`func1`と`func2`の関数表現が異なることに注目してください。`func1`には「**xの参照先は返値よりも先に死んではならない**」ことが表現されています。`func2`では「**yの参照先は返値よりも先に死んではならない**」ことが表現されています。よって、最初の例で使い方を守らなかったのは関数の使用者の責任であり、どのように間違え、どのように直すべきなのか、明確です。ライフタイム注釈がなんのためにあるのか。（パーサーにも大いにメリットはありますが）何よりも我々人類のため、実装者が設計意図を表明し、使用者がその意図を汲み取る、静的な型システムを成立させるためだったということです。

*[2024/06/01]コメント指摘により、C++との比較を削除*

# ライフタイム注釈の読み方
では、先ほどのライフタイム注釈の読み方について見ていきましょう。`fn`キーワードによる関数定義時に**引数と返値に同じ名前のライフタイム注釈がある場合、その引数の参照先はその返値よりも先に死んではいけません**。

例えば、
```rust
fn hoge<'a>(x: &'a str) -> &'a str {}
```
<img width="300" alt="pattern1" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/160540/3556d99f-c62d-bff7-dfdf-d56cebbf87a5.png">

であれば、「**xの参照先はこの返値（&'a str）よりも先に死んではならない**」と読みます[^1]。関数名`hoge`のあとの`<'a>`は、`'a`がライフタイム注釈であることを定義しています。要は`'b`でも`'aaa`でも好きに名付けて使って良いのですが[^4]、あらかじめ`<>`内で定義しておく必要があります。

次に、

```rust
fn hoge<'a>(x: &'a str, y: &'a str) -> &'a str {}
```
<img width="360" alt="pattern2" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/160540/10e7a5d2-a782-f546-f529-f5ac28f38f26.png">

であれば、「**xの参照先はこの返値（&'a str）よりも先に死んではならないし、かつyの参照先はこの返値（&'a str）よりも先に死んではならない**」と読みます。`x`と`y`は同じライフタイム注釈（`'a`）で表されていますが、これによってxとyの寿命関係が制限されることはありません。`fn`におけるライフタイム注釈は、あくまでも**返値から見た引数の参照先の寿命の下限を規定するもの**です。

次はどうでしょうか。

```rust
fn hoge<'a,'b>(x: &'a str, y: &'b str) -> &'a str {}
```
<img width="360" alt="pattern3" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/160540/d8973444-11e1-5c9a-2b91-26fe946c4f5f.png">

これは「**xの参照先はこの返値（&'a str）よりも先に死んではならない**」と読みます。異なるライフタイム注釈（`'a`と`'b`）で表される引数と返値の間には何の寿命制限も生まれません。

:::note
関数名のあとにいちいち`<'a,'b>`のように定義することは必要なのか？と思った人もいるかもしれません。これはジェネリクスの記法に準拠したものです。そもそもジェネリクスにおいてなぜこのような記述が必要かは[別記事](https://qiita.com/plotter/items/43ec5bced0585055e709)に書きました。もっとも、ライフタイム注釈における予約語はごく少数しかない（`'static`など）ので省略できる気もしますが、記法の統一性を重視したのではないかと思います。
:::

改めて、前章に戻って`func1`と`func2`の関数表現を読んでみて下さい。あるいは、[Rust公式チュートリアルの10.3章](https://doc.rust-jp.rs/book-ja/ch10-03-lifetime-syntax.html)の`longest`関数の表現を読んでみて下さい。今なら関数実装者の意図を読み取れるでしょうか？


# いつ注釈が必要なのか
前章では、関数の使用者の立場からライフタイム注釈の読み方を見ました。では、関数の**実装者**は**いつ**ライフタイム注釈をつけなければならないのでしょうか？前章で述べた通り、関数定義におけるライフタイム注釈は関数の使用者にその関数の正しい使い方を告知するものでした。よって「**ライフタイム注釈が無ければ使用者が誤った使い方をする可能性があるかどうか**」がライフタイム注釈の要不要を見分けるポイントになります。

`fn`キーワードによる関数定義の典型的なパターンを順番に見ていくことにしましょう。ただし、以下では簡単のために静的ライフタイム`'static`（プログラム実行中全域に渡って生きていることを表す特殊なライフタイム注釈）が絡まないパターンのみを想定します。`T`は参照を内部に含まない何らかの型を表します。

1. `fn hoge(x: T) -> T {}`
引数にも返値にも借用型が無いパターン。これまでずっと述べてきたとおり、ライフタイム注釈が規定するのは借用の寿命の話なので、明らかにライフタイム注釈は**不要**です。

1. `fn hoge(x: &T) -> T {}`
引数にだけ借用型があるパターン。最初に述べた通り、ライフタイム注釈は返値の寿命から見た引数の寿命を規定するものなので、（'static 絡みで無い限り）ライフタイム注釈は**不要**です。
1. `fn hoge(x: T) -> &T {}`
返値にだけ借用型があるパターン。引数に与えてもいないのに返値に参照があるということは、（'static 絡みで無い限り）関数内部の変数の参照を返しているとしか考えられませんが、関数から出た瞬間に関数内部の変数は破棄されるので**コンパイルできません**。
1. `fn hoge(x: &T) -> &T {}`
返値と引数に一つずつ借用型があるパターン。上記3.と同じ理由で、返値は必ず引数をそのまま返していることになります（'static 絡みで無い限り）。よって、引数xの参照先は返値よりも先に死んではいけません。だからライフタイム注釈をつける.....、と言いたいところですが、逆に**この形であればそれ以外のパターンがありません**。よって使用者が迷う余地は無い、ということで、**ライフタイム注釈は省略可能**になっています（ごく初期のRust[^3]では必要だったようですが）。
1. `fn hoge(x: &T) -> (&T, &T) {}` 
入力が1つ、出力が2つのパターン。上記3.と同じ理由で（'static 絡みで無い限り）返値はどちらも引数をそのまま返す形になります。よって上記4.と同じ形になり、**ライフタイム注釈は省略可能**です。
1. `fn hoge(x: &T, y: &T) -> &T {}` 
入力に複数借用型があるパターン。前章で述べた`func1`や`func2`のパターンですね。解説したとおり、**ライフタイム注釈が必要**です。返値がxなのかyなのか、あるいはどちらの可能性もあるのかによって注釈のつけ方が変わります。

ライフタイム注釈が必須となるケースは案外少ないですね。型内部に参照が含まれている場合・[匿名ライフタイム](https://zenn.dev/takanori_is/articles/anonymous-lifetime-the-lifetime-we-saw-that-day)が必要なる場合などはもう少し形が複雑になりますが、「いつ注釈が必要になるのか？」の判断方法自体はシンプルだと実感してもらえたでしょうか。

# おわりに
今回は`fn`キーワード関連に絞ってライフタイム注釈について説明をしました。ライフタイム注釈は`fn`キーワードのほかに`struct`や`impl`キーワード等でも登場しますが、基本的な考え方は同じです。

「型」が担う重要な仕事の一つとして、メモリ空間をどのように使うかを規定することが挙げられます。それに対して、オブジェクトの寿命という時間軸情報が型表現に含まれている時空間的な型システムがRustの面白いところだと思います。

[^1]: 文中でも述べましたが、現行バージョンのRustではこのパターンを使う機会はありません。
[^2]: 良くはない。ライフタイム注釈が無い場合、関数A内部で用いている関数Bのライフタイムをチェックして、さらに関数Bの内部で同じことをして......、というように（引数に参照を渡す限り）予想できない深さまでコンパイラがライフタイムを再帰的にチェックしなければならないので非常に大変。そしてその結果として10個下の階層におけるライフタイム違反が検知されたとして、ユーザーはそのエラーを理解出来るだろうか？
[^3]: Must（Meccha-lifetime-annotation-wo-kakanakerebanaranai-rUST）
[^4]: シングルクオーテーション`'`は必須