コンテナの値を取って→入れて→エラー (lifetimeとかの話)

#はじめに

Rustのコンテナを使うときは、所有権を強く意識する必要があります。 `HashMap`はその代表的な例で、`HashMap`に要素を挿入するときは値渡しを要求され、`HashMap`の要素の値を見るときには参照渡しでしか返してくれません。 つまり`HashMap`は挿入された要素の所有権を奪うぞということで、まぁそういうものだと言われればそうだよねという話なのですが、ここでよくハマるポイントがあります。

```rust:フィボナッチ数列のHashMapを作ってみる例
use std::collections::HashMap;

fn main() {
    let mut fib : HashMap<i32, i32> = HashMap::new();
    fib.insert(1, 1);
    fib.insert(2, 1);
    fib.insert(3, 2);
    fib.insert(4, 3);
    fib.insert(5, 5);

    let fib4 : &i32 = fib.get(&4).unwrap();
    let fib5 : &i32 = fib.get(&5).unwrap();
    // コンパイルエラー！
    // cannot borrow `fib` as mutable because it is also borrowed as immutable
    fib.insert(6, fib4 + fib5);
}
```

この例ではフィボナッチ数列の`HashMap`を作っています。 フィボナッチ数列の6番目は4番目+5番目ですから、当然上のようにして作成するのですが、これはコンパイルが通りません。
コンパイルエラーは分かりやすく、『`fib`はすでに参照が使われているから、`&mut self`な呼び出しの`insert`メソッドは使えないよ』みたいなことを言ってくれます。 **ある変数への`mut`参照と非`mut`参照は同時に存在できない**ということは、Rustの参照を最初に学ぶときに口を酸っぱくして言われることなので、このコンパイルエラーにも納得してしまいそうになりますが、**ちょっと待ってください**。 **上のコードのどこに、`fib`への『生きてる』参照があるんだ？**

確かにこの行で、`fib`への参照を**利用**してはいます。

```rust
    let fib4 : &i32 = fib.get(&4).unwrap();
```

`HashMap::get`メソッドの第1引数は`&self`ですから、`get`メソッドに渡っているのは`fib`への参照です。 しかし、ここでは参照を一時的に作って渡しているだけで、よく例題に出されるようなこんな文とは明らかに異なります。

```rust:よく所有権の例題で出されるやつ
fn owner() {
    let mut x = 100;
    let ref_x = &x;

    // 非mut参照とmut参照は同時に存在できないのでエラーになる
    let mut_ref_x = &mut x;
}
```

この例題では、明らかに`x`への参照を変数`ref_x`に保持しています。 なので、その下で`x`への`mut`参照を取ろうとしてエラーになるのはよく分かります。 しかし、フィボナッチ数列の例では、`fib`への参照は一時的に使われているけれども、その結果変数`fib4`に代入されているのは`&i32`型です。 **`i32`への参照を`fib4`に入れてるだけなのに、なんで`fib`への参照を作っているようにコンパイラは解釈してしまうんだ？** という疑問が当然湧き上がります。

## 参照のlifetime

`HashMap`の`get`メソッドの定義を見てみましょう。 こんな風になっています：

```rust
    fn get(&self, key: &Q) -> Option<&K>;
```

[Rust公式ドキュメントのLifetimesの項](https://doc.rust-lang.org/stable/book/lifetimes.html#lifetime-elision)を読むと書いてあるのですが、実はこれは省略された記法で、ちゃんと省略せずに書くとこのようになります：

```rust
    fn get<'a, 'b>(&'a self, key: &'b Q) -> Option<&'a K>;
```

`'a`とか`'b`とか、Rustのドキュメントを読んでいると頻繁に出てくる記法ですが、難しめなので初心者が無意識に読み飛ばしてしまいがちなところです。 この`'`から始まる(たいてい)1文字の名前は、関数や構造体のメンバ宣言で参照を使うときはかならず意識しなければならないlifetimeに関する記号です。 基本的に、**ローカル変数宣言以外で型の名前に`&`を付けて書くときは、必ずこの`'a`のような記号もいっしょに書く必要がある**と考えてください。 コンパイラ様のご厚意により、初心者向けの題材で出るような簡単なケースではこれが省略できるようになっていますが、Rustを書いていればすぐにこれが省略できないケースにぶち当たります。

で、この`'a`や`'b`が何を意味するのか、ということですが、注目すべきは引数と戻り値のなかにある参照のうち、どれとどれが同じ`'a`や`'b`を持っているか、ということです。 ここでは、第1引数の`&self`と戻り値の`Option`の中身の`&K`が同じ`'a`を持っていますね。 これは、lifetimeという言葉を使って書き下すと、『`&self`と`&K`は同じlifetimeを持つよ』と言っていることになります。 しかしそもそもlifetimeという言葉の解説をしていない(むずかしいから)ので、さっきのフィボナッチ数列のケースに当てはめてさらに言い直すと、「**`&K`(フィボの例では`&i32`)に対する参照は、`&self`に対する参照と同じものだとみなして同時複数参照のチェックをするからよろしく**」という意味になります。

これで、最初のフィボナッチ数値の例でコンパイルエラーになる原因が分かりました。 `get`メソッド宣言中のlifetimeにより、`fib4`が保持している`i32`への参照は、`fib`への参照と同じものとして扱われるので、`fib4`が生きている状態で`fib`への`mut`参照を取ってくることはできないのです。

## 代わりにどうすればいいか

しかし、しかしです。 フィボナッチ数列の4つめと5つめの値を足して6つめの値を計算したいんです。 どうしてもしたいんです。 どうすればコンパイラを満足させるようなコードが書けるのでしょうか？

方法の1つとしては、`fib4`と`fib5`を取る時に、参照を断ち切ってしまうという手があります。 単純に、`*`をつけて参照外しをしてしまうのです。

```rust
    let fib4 : i32 = *fib.get(&4).unwrap();
    let fib5 : i32 = *fib.get(&5).unwrap();
    fib.insert(6, fib4 + fib5);
```

先ほどの例と比べて、`fib4`と`fib5`が`&i32`型から`i32`型になっていることに注目してください。 参照で死ぬなら、参照なんて保持しなければええんや！ というわけで、これは一つの立派な解決策です。 ただし問題があり、これでは`i32`のコピーが発生してしまいます。 ここでは`i32`ですからそのコストなんて微々たるものですが、これがもっとデカい型だったときには目も当てられませんし、そもそもコピーできない型だってあります。

そんな場合には、こんな解決法があります。

```rust
    let fib6 : i32 = {
        let fib4 : &i32 = fib.get(&4).unwrap();
        let fib5 : &i32 = fib.get(&5).unwrap();
        fib4 + fib5
    };
    fib.insert(6, fib6);
```

要するに、要件として

- 6番目のフィボナッチ数を作るのに、4番目と5番目のフィボナッチ数は絶対に必要
- でも、6番目のフィボナッチ数を`fib`に挿入する時に、`fib4`と`fib5`に生きていてほしくない

ということなので、じゃあ`fib`への挿入と、6番目のフィボナッチ数(`fib6`)の作成を分けてしまえばいいじゃんってことになります。 `fib6`の初期化はあんまり見ない構文ですが、こういうやり方もできるということでよろしくお願いします(`fib4 + fib5`のあとにセミコロンがない点に注意)。 この構文の何が嬉しいかというと、`fib6`ができあがったあとには、`fib4`も`fib5`もどちらももう死んでいることです。 なので、遠慮なく`fib`への`mut`参照呼び出しをすることができるのです。 しかも`i32`のコピーが発生していません。 上の書き方に比べてほぼあらゆる面で優れていますが、読みやすさだと上のコードのほうがちょっといい気がするので、コピーのコストが問題にならないプリミティブ型なら上のコードで書いてしまっていいでしょう。