Sync
とSend
トレイトで拡張可能な並行性
面白いことに、Rust言語には、寡少な並行性機能があります。この章でここまでに語った並行性機能のほとんどは、 標準ライブラリの一部であり、言語ではありません。並行性を扱う選択肢は、言語や標準ライブラリに制限されません; 独自の並行性機能を書いたり、他人が書いたものを利用したりできるのです。
ですが、2つの並行性概念が言語に埋め込まれています: std::marker
トレイトのSync
とSend
です。
Send
でスレッド間の所有権の転送を許可する
Send
マーカートレイトは、Send
を実装した型の所有権をスレッド間で転送できることを示唆します。
Rustのほとんどの型はSend
ですが、Rc<T>
を含めて一部例外があります: この型は、Rc<T>
の値をクローンし、
クローンしたものの所有権を別のスレッドに転送しようとしたら、両方のスレッドが同時に参照カウントを更新できてしまうので、
Send
になり得ません。このため、Rc<T>
はスレッド安全性のためのパフォーマンスの犠牲を支払わなくても済む、
シングルスレッド環境で使用するために実装されているわけです。
故に、Rustの型システムとトレイト境界により、Rc<T>
の値を不安全にスレッド間で誤って送信することが絶対ないよう保証してくれるのです。
リスト16-14でこれを試みた時には、the trait Send is not implemented for Rc<Mutex<i32>>
というエラーが出ました。
Send
のArc<T>
に切り替えたら、コードはコンパイルできたわけです。
完全にSend
の型からなる型も全て自動的にSend
と印付けされます。生ポインタを除くほとんどの基本型もSend
で、
生ポインタについては第19章で議論します。
Sync
で複数のスレッドからのアクセスを許可する
Sync
マーカートレイトは、Sync
を実装した型は、複数のスレッドから参照されても安全であることを示唆します。
言い換えると、&T
(T
への参照)がSend
なら、型T
はSync
であり、参照が他のスレッドに安全に送信できることを意味します。
Send
同様、基本型はSync
であり、Sync
の型からのみ構成される型もまたSync
です。
Send
ではなかったのと同じ理由で、スマートポインタのRc<T>
もまたSync
ではありません。
RefCell<T>
型(これについては第15章で話しました)と関連するCell<T>
系についてもSync
ではありません。
RefCell<T>
が実行時に行う借用チェックの実装は、スレッド安全ではないのです。
スマートポインタのMutex<T>
はSync
で、「複数のスレッド間でMutex<T>
を共有する」節で見たように、
複数のスレッドでアクセスを共有するのに使用することができます。
Send
とSync
を手動で実装するのは非安全である
Send
とSync
トレイトから構成される型は自動的にSend
とSync
にもなるので、
それらのトレイトを手動で実装する必要はありません。マーカートレイトとして、
実装すべきメソッドさえも何もありません。並行性に関連する不変条件を強制することに役立つだけなのです。
これらのトレイトを手動で実装するには、unsafeなRustコードを実装することが関わってきます。
unsafeなRustコードを使用することについては第19章で語ります; とりあえず、重要な情報は、
Send
とSync
ではない部品からなる新しい並行な型を構成するには、安全性保証を保持するために、
注意深い思考が必要になるということです。The Rustonomiconには、
これらの保証とそれを保持する方法についての情報がより多くあります。
訳注: 日本語版のThe Rustonomiconはこちらです。
まとめ
この本において並行性を見かけるのは、これで最後ではありません: 第20章のプロジェクトでは、 この章の概念をここで議論した微小な例よりもより現実的な場面で使用するでしょう。
前述のように、Rustによる並行性の取扱いのごく一部のみが言語仕様なので、多くの並行性の解決策は クレートとして実装されています。これらは標準ライブラリよりも迅速に進化するので、 マルチスレッド環境で使用すべき現在の最先端のクレートを必ずネットで検索してください。
Rustの標準ライブラリは、メッセージ受け渡しにチャンネルを、並行の文脈で安全に使用できる、
Mutex<T>
やArc<T>
などのスマートポインタ型を提供しています。型システムと借用チェッカーにより、
これらの解決策を使用するコードがデータ競合や無効な参照に行き着かないことを保証してくれます。
一旦コードをコンパイルすることができたら、他の言語ではありふれている追跡困難な類のバグなしに、
複数のスレッドでも喜んで動くので安心できます。並行プログラミングは、もはや恐れるべき概念ではありません:
恐れることなく前進し、プログラムを並行にしてください!
次は、Rustプログラムが肥大化するにつれて問題をモデル化し、解決策を構造化する慣例的な方法について話します。 さらに、Rustのイディオムがオブジェクト指向プログラミングで馴染み深いかもしれないイディオムにどのように関連しているかについても議論します。