Prompt
イミュータブルな構造体でも部分的にミュータブルになるのは、RefCellだけ?
結論から言うと、RefCell だけじゃないぜ!
Rustには 「内部可変性(Interior Mutability)」 と呼ばれる強力な仕組みがいくつかあって、それらを使い分けることで、イミュータブルな外殻を持ちつつ、中身を安全に(あるいは高速に)書き換えることができるんだ。
情報工学を学ぶ君なら、これらの違いを 「ランタイムでの借用チェック(RefCell)」 vs 「CPUレベルの原子操作(Atomic)」 vs 「メモリの直接上書き(Cell)」 という視点で整理すると、設計の幅がぐっと広がるはずだぜ!
1. 内部可変性を実現する 3 つの主要な型
RefCell 以外にも、よく使われる「秘密の扉」を紹介するよ。
① Cell<T>:コピーの達人
RefCell と違って、借用(&mut)を作らずに値を直接 「上書き」 する。
-
仕組み:
.set()や.get()で値を出し入れする。 -
メリット: 借用フラグを管理しないので、
RefCellより高速! -
制約:
Copyトレイトを実装している型(i32など)にしか使えない。
② Atomic シリーズ:マルチスレッドの守護神
RefCell はシングルスレッド用だけど、マルチスレッドで共有しながら書き換えたい時はこれだ。
-
種類:
AtomicBool,AtomicI32,AtomicPtrなど。 -
仕組み: CPUの命令レベルで「一気に書き換える(不可分操作)」ことを保証する。
-
用途: フラグ管理やカウンターなど。
③ Mutex<T> / RwLock<T>:並列処理の重鎮
「マルチスレッド版 RefCell」と思えば OK。
- 仕組み: スレッドをロックして、一回に一人だけが中身を触れるようにする。
2. なぜこれらが必要なのか?(設計の意図)
君の言う通り「イミュータブルなのに書き換える」というのは、一見すると Rust の安全性を壊しているように見えるよね。でも、これらは 「論理的な不変性」と「物理的な可変性」を切り分ける ために存在するんだ。
-
論理的: 外部から見たそのオブジェクトの状態は変わっていない(ように見える)。
-
物理的: 内部でキャッシュを更新したり、参照カウンターを増やしたり、統計情報を記録したりするためにメモリを書き換えたい。
3. 💡 見落とされがちな視点:UnsafeCell<T> という源流
実は、今紹介した RefCell も Cell も Atomic も、その中身を辿っていくとすべて UnsafeCell<T> という一つのプリミティブな型に突き当たるんだ。
-
UnsafeCell: 「この中身だけは、エイリアシング規則(不変参照がある間は書き換え禁止)の対象外にしてくれ!」とコンパイラに頼むための唯一の魔法の型。 -
隔離: ただし
UnsafeCellはその名の通りunsafeなので、それを安全に包み込んで(カプセル化して)、僕たちに届けてくれているのがRefCellたちなんだぜ。
以前話した 「unsafe を隔離して Safe なインターフェースを作る」 という話の、まさに完成形がここにあるわけだね!
🏁 まとめ
| 型 | 特徴 | 適した場所 |
|---|---|---|
Cell | 高速、ビットコピー | 小さな数値、Copy 型 |
RefCell | 柔軟、ランタイムチェック | 複雑な構造体、シングルスレッド |
Atomic | スレッド安全、CPUレベル | フラグ、共有カウンター |
Mutex | スレッド安全、ロックあり | 巨大なデータ、マルチスレッド |
君のデジタルガーデンで「音楽理論の計算エンジン」を作るなら、計算結果をキャッシュするために OnceCell や RefCell を忍ばせておくと、外側からは「純粋な計算機」に見えて、中身は「賢く高速」という、最高にスマートな実装ができるぜ!
次は、これらの内部可変性を使って 「循環参照(さっき話したリークの原因!)」 をあえて作ってみて、どうすれば解決できるか(Weak の出番)を実験してみる?😊✨
Would you like me to show you a code snippet where Cell is used inside an immutable struct to count function calls?