このページの目次
Send と Sync
Send と Sync は Rust の並行安全性の基盤です。Send は所有権をスレッド間で移動可能にし、Sync は参照をスレッド間で共有可能にします。これらは auto trait(コンパイラによる自動推論)であり、ある型のすべてのフィールドが Send であれば、その型も自動的に Send となります。PhantomData は、自動推論を手動で抑制または復元するために使用され、不安全コードや FFI において極めて重要です。
Send と Sync とは
Send と Sync は Rust の並行安全性の中核をなすものです。これらはコンパイル時のマーカートレイトであり、ある型がスレッド間で使用可能かどうかを示します。
Send: この型の所有権をスレッド間で移動できます。ある型のすべてのフィールドが Send であれば、その型も Send となります——これはコンパイラによって自動で推論されます。Rc<T> は Send ではありません——参照カウント操作がアトミックではないため、別のスレッドに移動するとカウントが壊れる可能性があります。
Sync: この型を共有参照(&T)経由でスレッド間でアクセスできます。形式的な定義は次の通りです:T: Sync であるための必要十分条件は &T: Send です。RefCell<T> は Sync ではありません——実行時の借用チェックがスレッドセーフであるとは保証されないためです。Mutex<T> は Sync です(T: Send の場合)——Mutex は内部をロックで保護し、複数のスレッドが &Mutex<T> 経由で安全にアクセスできるようにします。
コンパイラによる推論
コンパイラは型の各フィールドをチェックします。すべてのフィールドが Send であれば、その型は自動的に Send となります。すべてのフィールドが Sync であれば、その型は自動的に Sync となります。これらの属性を失うのは、裸のポインタ(*const T, *mut T)やスレッドセーフでないプリミティブ(Rc, RefCell, Cell, UnsafeCell)を導入した場合のみです。
; // OK
; // OK (Arc はアトミック操作で同期)
// assert_send::<Rc<String>>(); // コンパイルエラー
// assert_send::<*const i32>(); // コンパイルエラー — 裸のポインタは所有権セマンティクスを持たない
; // OK (Mutex は内部同期を保証)
// assert_sync::<RefCell<i32>>(); // コンパイルエラー
PhantomData による手動制御
裸のポインタを使用してカスタムコンテナを実装する場合、裸のポインタ自体は Send/Sync の属性を持ちません。しかし、論理的にはこれらの保証があるかもしれません:
use PhantomData;
use Rc;
// MyNonSend の内部には i32(Send である)しかないが、PhantomData<Rc<()>> により !Send となる
PhantomData はメモリを消費しません(ZST: Zero-Sized Type)。型レベルでのみマーキングを行います。unsafe コードの作者は、PhantomData を使用して「この型は Send/Sync であるべき/であるべきではない」ということをコンパイラに伝え、コンパイラが呼び出し側に対して正当性のチェックを行うようにします。
なぜ「すべての型がデフォルトで Send」ではないのか
標準ライブラリの型は慎重に設計されています。Rc<T> は本質的に Send ではなく、Mutex<T> は本質的に Sync です。サードパーティ製のライブラリにおいて、内部可変性を導入する型は、Send/Sync の伝播を慎重に検討する必要があります。ある型が実際にはスレッドセーフではないのに、誤って Send となってしまうと、それはデータ競合の抜け穴となってしまいます。この「自動推論+人間の介入が可能」という設計により、Rust はパフォーマンス(ゼロオーバーヘッド)と安全性(コンパイル時の保証)のバランスを取っています。
参考
- Rustonomicon: Send and Sync
- Rust Reference: auto traits
Keywords: Send, Sync, auto trait, PhantomData, thread safety, marker type, Rc, RefCell