Rustのすごいところを実例を用いて紹介します

Rustのすごいところを、ゲーム開発を例として紹介します。

対象読者：
- C++ 中級者レベル
- Rust 初心者
- Rust に興味がある人


#### ある開発現場での会話
```
デバッグ担当「アプリがクラッシュすることがあるみたいです」
エンジニア「え、どういう時に落ちるの？」
デバッグ担当「デバッグでは再現しません。ユーザーさんの声を見てると、○○バトルの時に落ちやすいみたいですね」
エンジニア「うーん、再現方法がわからないと、調査のしようが無いんだよね・・」
デバッグ担当「うーん・・」
エンジニア「うーん・・」

そして時は流れていった。

```

C++で大規模なプログラムを書いたことのある人なら、こんな経験、ありますよね！？
こんな状況が起こった時、**何かの条件で、どこかでメモリ破壊が起こっている、とは想像できるのですが、どこで、どうやってそれが起こるのか、調べるのはすごく大変です。**

なぜメモリ破壊が起こるのか。それは、あるメモリ領域が、いつどこから変更されるのかを、誰も把握できないからだ。（チームで開発するならなおさら！）

それなら、メモリの変更をコンパイラが把握できるようにしよう、というのが、Rustです。

### メモリが危険なプログラムをC++とRustで書いてみる

例として、RPGなどで良くある、あるキャラが他のキャラを召喚するという処理を書いてみます。

#### C++

まずはC++で書いてみましょう。

```cpp:game_object.hpp

#pragma once

class GameObjectManager;

//ゲームキャラなどの各種オブジェクトを表現するクラス
class GameObject {
public:

	GameObject();

	//一定時間毎に呼ばれる更新処理
    void Update(GameObjectManager* manager);

	//召喚できるかを判定する
	bool CanSummon();
};
```

```cpp:game_object.cpp
#include "game_object.hpp"
#include "game_object_manager.hpp"

GameObject::GameObject()
{
}

void GameObject::Update(GameObjectManager* manager)
{
	//召喚可能だったら、マネージャに召喚を依頼
	if(CanSummon()) {
		manager->SummonObject();
	}
}

bool GameObject::CanSummon() {
	//召喚可能かを判定するロジック
	return true;
}
```

```cpp:game_object_managar.hpp
#pragma once

#include "game_object.hpp"
#include <vector>

//ゲーム内に存在するGameObjectを管理するクラス
class GameObjectManager {
private:
    std::vector<GameObject> _game_objects;
public:

	GameObjectManager();

	//一定時間毎に呼ばれる更新処理
    void Update();

    //キャラを召喚する
    void SummonObject();

};
```

```cpp:game_object_managar.cpp
#include "game_object_manager.hpp"

GameObjectManager::GameObjectManager()
{
}

void GameObjectManager::Update() {
    //全てのオブジェクトをUpdate
    for(GameObject& obj : _game_objects) {
		obj.Update(this);
	}
}

void GameObjectManager::SummonObject() {
    //新しいオブジェクトを生成し、vectorに追加
	_game_objects.emplace_back(GameObject());
}
```

オブジェクト指向で素直に書くと、こんな感じになるでしょうか。あるキャラ（GameObject）が、召喚可能と判断すると、マネージャークラスを通じてキャラを召喚、_game_objectsに追加する。完璧ですね！

しかしこのコード、落とし穴があるのです。実際に動かしてみると、キャラが一定数増えた後、誰かが召喚をすると、しばらくしてプログラムがクラッシュしたりするのです。でも、必ずこうすればクラッシュする、と決まっていないのです。

一体この謎のエラーの原因はなんでしょうか？処理の流れを追ってみましょう。

```
GameObjectManager::Update()が呼ばれる
  _game_objectsそれぞれについて
  {
　  GameObject::Update()を呼ぶ
    一定の条件で、GameObjectManager::SummonObject()が呼ばれる
    _game_objectsに新しいGameObjectを追加する。
  }
```

よく見ると、_game_objectsのループ中に、_game_objectsに追加していることがわかります。
std::vectorは可変長の配列で、メモリの許す限り、データを追加することができます。

vector内部では、一定数のメモリ領域を予約していますが、その予約した領域を越えるデータを
追加しようとした時、新たにメモリを確保しなおし、内部のデータを全部そこに移動するんですね。

もし、GameObjectManager::SummonObject()の時に、メモリの再配置が行われた場合、
GameObjectManager::Update()で使っていたイテレータは、もう無効なアドレスを指すことになってしまいます。

無効になったイテレータの参照先が、別の処理によって確保された、違う用途のメモリ領域になることもあります。そうするとメモリ破壊が起こり、何かのきっかけでプログラムがクラッシュします。

**問題は、このような潜在的な問題に、ある程度経験のあるプログラマでなければ、気づけないということです。**

#### Rustで書いてみる

さて、上記のようなコードをRustで書いてみると、どうなるでしょうか。

本稿の読者の方は、Rust初心者だと思いますので、コード一つ一つに解説をつけています。

```Rust

//Rustには class はなく structのみ
struct GameObject {
}

//GameObjectの実装
impl GameObject {

    //selfはC++で言うthisのこと
    //&は参照を意味する
    //mutはミュータブル（変更可能）であることを意味する
    //GameObjectのupdateは自身の状態を変化させるので、&mut self とする
    //さらに、自身を管理するGameObjectManagerに対して、何か変更を加える可能性があるので、&mut GameObjectManager となる
    fn update(&mut self, manager: &mut GameObjectManager) {
        if self.can_summon() {
            manager.summon_object();
        }
    }

    //召喚可能かを返す関数
    //selfの中身を変更することはないので、mutは付けない
    fn can_summon(&self) -> bool {
        //selfを使って何か状態をチェック
        true    //セミコロンを付けずに書くと return true; の意味になる
    }
}

struct GameObjectManager {
    //VecはC++で言うstd::vector
    game_objects: Vec<GameObject>
}

impl GameObjectManager {

    //自身の持つ変数に変更を加えるので、&mut self
    fn update(&mut self) {

        //各GameObjectのミュータブルな参照を取ってupdateを呼ぶ
        //C++と違い、参照を得る際にも & が必要
        for o in &mut self.game_objects {
            o.update(self);
        }
    }

    //召喚
    //game_objectsに変更を加えるので&mut self
    fn summon_object(&mut self) {
        self.game_objects.push(GameObject{});
    }
}
```

mutという見慣れないキーワードができました。

mutとはmutableのことで、簡単に言うと「これからこのオブジェクトを変更するよ、という意味です。C++のconstの逆みたいなものか、と思うかも知れませんが、そうでもありません。

詳しくは割愛しますが、ここで理解してもらいたいのは、 **Rustでは、オブジェクトのミュータビリティ（変更を加える事）をコンパイラが理解して、危険な変更はできないようにチェックしてくれる** 、ということです。

このプログラムをコンパイルしてみると、以下のようなエラーが出力されました。

```
error[E0499]: cannot borrow `*self` as mutable more than once at a time
  --> src/main.rs:27:22
   |
26 |         for o in &mut self.game_objects {
   |                  ----------------------
   |                  |
   |                  first mutable borrow occurs here
   |                  first borrow used here, in later iteration of loop
27 |             o.update(self);
   |                      ^^^^ second mutable borrow occurs here

error: aborting due to previous error

For more information about this error, try `rustc --explain E0499`.
```
順番に見ていきましょう。

```
error[E0499]: cannot borrow `*self` as mutable more than once at a time
```

翻訳すると、自身のミュータブル参照を一度に２回以上取得することはできません。
となるでしょうか。（borrowとは、&を使って参照を得ることです）

そして、エラーの詳細が出ています。（とってもわかりやすい！）

```
26 |         for o in &mut self.game_objects {
   |                  ----------------------
   |                  |
   |                  first mutable borrow occurs here
```

まずここで、最初のミュータブル参照が起こっている、とのことです。自身のメンバ変数へのミュータブル参照を得ることは、自身へのミュータブル参照を得ることにもなるんですね。

そして、

```
27 |             o.update(self);
   |                      ^^^^ second mutable borrow occurs here
```

ここが２回目のミュータブル参照だと。
GameObject::updateは、GameObjectManagerへのミュータブル参照を取っていますからね。

Rustでは、参照に関して、以下のどちらかの状態であることをチェックします。

 * ミュータブル参照が、１つだけある状態。この場合はイミュータブル（mutの付かない）参照も含めて、他の参照が１つでもあってはならない。
 * イミュータブル参照が、複数ある状態（ミュータブル参照はなし）

この条件に合わない参照がなされていた場合は、コンパイルエラーとなるんですね。
もう少しわかりやすく言うと、

 * 誰かが書き込んでいる時は、他の人は一切触っちゃだめ。見てもだめ。
 * 見るだけなら、複数同時に見るのはOK

こんな感じでしょうか。

この制限を課すことで、メモリの安全性を保証している訳です。

さて、コンパイルが通りません。困りましたね。

要は、GameObjectが、自身を管理するGameObjectManagerに変更を加える、という設計がそもそもダメな様です。実際、前述のC++の方では、この設計をしたことで、メモリ破壊が起きてしまいました。

そう、C++ではランタイムエラーとなるところを、Rustではコンパイルエラーにしてくれるのです！
**ランタイムエラーとコンパイルエラー、バグの調査がどちらが楽かは、明らかですよね！**
Rustすごい！

これが、筆者がRustをお勧めする理由の一つです。

Rustを勉強していると、C++ってどうしてこんなにも、なんでもありなんだ！と思う様になります。
まるで、信号も車線もない道路の様。怖くて走れません。

Rustを実際に業務で使うことがなくても、Rustの考え方を学ぶことで、C++や他のプログラミング言語を書くときも、メモリ安全性を考慮して書けるようになります。

この機会に、Rustを勉強してみてはいかがでしょうか？