"独占こそが秩序を生む。だが、時に我々は、秩序のために独占を手放すしかない。"
Rustの原則は、すべてのリソースに明確な“所有者”を定めることにある。
このルールによって、メモリ管理は構文的に保証され、破壊的なバグが生まれにくくなる。
しかし現実の設計では、「1つの値を複数の場所で共有したい」という要求が避けられない。
Rustはこの矛盾に対して、“制限付きでの妥協”という設計的解決を提示する。
それが Rc<T>
と Arc<T>
である。
この章では、所有権を共有するという選択が、どのように構文化されているか、
そしてなぜそれが「例外」ではなく「設計的に制御された逸脱」であるかを読み解いていく。
Rc:参照カウントによる“構造的妥協”
use std::rc::Rc;
let a = Rc::new(String::from("shared"));
let b = Rc::clone(&a);
このコードでは、a
と b
が同じデータを指している。
通常の String
であれば、ムーブによって a
は無効になるが、Rc
は**参照カウント(Reference Counting)**を用いて複数の所有者を許容する。
重要なのは:
-
Rc
はコピーではない(Cloneは浅いコピー) - 破棄は最後の参照がDropされたときにのみ実行される
- 所有は“共有されている”が、可変性は封印されている
つまり、Rc
は可変性を捨てることで共有を可能にした妥協の構文である。
可変にできない理由:破壊的同時性を防ぐ設計倫理
Rc<T>
は基本的に 不変の値 を共有するための仕組みであり、同時に複数が書き換えることは許されない。
この制限は、安全性の保証と、実行時の複雑性を抑えるために存在する。
-
Rc<RefCell<T>>
のように内側で可変化はできる - だがそれは、設計者が意図的に“内部可変性”を選んだという明示的表明を要求する
Rustは、“安全な状態を構文で維持しながら、必要な場合にだけ封印を解く”という設計哲学を貫いている。
Arc:スレッドを越える所有のための構文的儀式
use std::sync::Arc;
let a = Arc::new(String::from("shared across threads"));
let b = Arc::clone(&a);
Arc<T>
は Rc<T>
のスレッドセーフ版であり、atomicな参照カウントによってスレッド間の安全な共有を可能にしている。
-
Rc<T>
:シングルスレッド向け(非Sync) -
Arc<T>
:マルチスレッド向け(Sync + Send)
つまり、「設計がスレッドをまたぐ」ことを構文で明示するために、Arc
は存在している。
Rustは、スレッド境界を越えるという設計上の“重み”を、明確な型として設計に刻む。
所有を共有する代償:「コスト」は安全の値札である
Rc
や Arc
の使用にはランタイムコストが伴う:
- カウントのインクリメント/デクリメント
- アトミック演算(Arc)による同期オーバーヘッド
- 可変性が必要な場合のRefCellやMutexなどのラップ
これらは決して“悪”ではない。
むしろ、「本当に共有が必要なときだけ明示的に選べ」という構文的な設計制御である。
Rc/Arcが設計にもたらす“選択と責任”
Rustでは、共有はデフォルトではない。
だが、どうしても必要なときには選べるようになっている。
- 必要最小限の共有だけを許容する
- “コストがある”という事実を設計者に突きつける
- “意図して共有している”という責任をコードに刻ませる
このスタンスは、自由ではなく“慎重な選択肢”としての自由である。
RcとArcは「秩序ある例外」として設計されている
共有は常に混乱を呼ぶ。
だからRustは、構文によってその混乱を制限し、“例外”を明示的に管理できるようにしている。
- 単一の所有権:デフォルトの秩序
- Rc / Arc:必要なときにのみ明示的に使える妥協構造
- RefCell / Mutex:その中でもさらに制御を付与した構造的緩和
これは、自由と安全のバランスを構文で設計する試みに他ならない。
結語:共有は逃避ではなく、設計された選択でなければならない
Rustにおける Rc
や Arc
は、安全で壊れにくい構造を維持しながら、必要最小限の共有を可能にする設計的手段である。
「すべてが共有される世界」では、所有は曖昧になる。
「すべてが単独所有でなければならない世界」では、表現力が失われる。
Rustは、その中間にある「構造的に制限された自由」を目指して、Rc
と Arc
を提供した。
"共有は、誤解を招く自由であってはならない。意図された構文として初めて、それは信頼となる。"