RustとOwnershipとMultithread

そろそろRust言語でWebアプリを作ってみようと思ってから1か月。予想はしていましたが、想像以上に進捗が悪いです。c++がわかるならRustもいけるという考えは幻想です。概念が理解できても、Rustの流儀を学ばないとコードは書けません。

実装者の性善説に基づくc++のコードを、Rustは許してくれません。例えば、以下のルールを破るコードはコンパイルエラーになります。

  • オブジェクトの保持と破棄を担う存在が唯一であること
  • 書き換えができる mutable reference が唯一であること
  • mutable referenceとimmutable referenceが同時に存在しないこと

例えば、メインスレッドでnewしたインスタンスを別のスレッドと共有し、mutexで保護しながら読み書きを行い、プログラム終了時はメインスレッド以外を停止してからメインスレッドでdeleteするプログラムを書くとしましょう。何の変哲もない実装ですが、Rustだと結構ややこしかったりします。

Rustの場合、スレッド間で共有するインスタンスは Arc<Mutex<Obj>> と定義します。Arcはマルチスレッド対応の参照カウンタで、所有者を2つ以上に増やしたい場合に必要になります。インスタンスをスレッド間で共有するマルチスレッドプログラミングにおいては事実上必須です。インスタンスをヒープ上に生成したのだから、解放するまでは生ポインタを受け渡しすればいい、みたいな甘い設計は許されません。

Mutexは排他制御を行う同期プリミティブですが、Rust言語ではインスタンスを内包するように使用します。スレッド内部で書き換え可能な参照を取得するために必要です。データ競合しても構わないからと、データを保護せずに値を書き換えることができません。

この2つの使い方さえ覚えてしまえば、あとは流れで理解できそうです。インスタンスをヒープ上に確保するBoxはc++のunique_ptrであるだけでなく、別スレッドと共有しないことを示します。Rcshared_ptrですが、Arcのようにマルチスレッド対応ではないので、同一スレッド上で共有されるインスタンスであることを示します。

ロックはMutexで説明しましたが、RwLockで保護しても構いません。AtomicBoolなどを使ってもいいでしょう。

Cell, RefCellも時たま登場します。これは所有者が2つ以上存在し、さらに異なる場所から書き換えを許容するときに使います。

Arc単体では共有できるだけで、値の書き換えはできません。このあたりの仕組みを深く知りたい場合は、内部可変性(Interior Mutability)でググるといいかもしれません。