このページの目次
Box, Rc, Arc
Box は単一所有者のヒープ割り当て(最もシンプル)、Rc はシングルスレッドの参照カウント(共有所有権)、Arc はマルチスレッドの原子参照カウントです。Weak は循環参照を解消し、メモリレイアウト上、Rc/Arc の値と参照カウントはヒープ上で隣接して割り当てられます——これら3つを理解することは、Rust の「GC は使わないが参照カウントがある」という仕組みを理解することに他なりません。
Box: 単一所有者のヒープ割り当て
let b = Boxnew; // ヒープ上に i32 を割り当てる
// stack: [8バイトのポインタ] → heap: [4バイトの値: 42]
Box<T> は Rust で最もシンプルなヒープ割り当てです——単一所有者であり、参照カウントを持ちません。b がスコープを外れると、ヒープメモリは解放されます。C++ の std::unique_ptr<T> に似ていますが、Rust のバージョンではムーブセマンティクスのキーワードは不要です——所有権はコンパイル時に保証されます。
Box の使用タイミング
- 再帰型: Rust では型サイズをコンパイル時に知らなければなりません。
enum List { Cons(i32, List), Nil }とすると再帰によりサイズが無限大になります。解決策:Cons(i32, Box<List>)——Box のサイズは固定(ポインタ、8バイト)であり、再帰はヒープ経由で間接的に解決されます - トレイトオブジェクト:
Box<dyn Display>は、Display を実装した任意の型を格納できます - 大きな値をスタックからヒープへ:
[u8; 1000000]のような大きな値がある場合、Box::new([0u8; 1000000])を使うと、スタック上には 8 バイトのポインタしか置かれなくなります - DST (動的サイズ型):
Box<[T]>,Box<str>,Box<dyn Trait>
Rc: シングルスレッドの参照カウント
use Rc;
let a = new; // strong_count = 1
let b = clone; // strong_count = 2、Vec のデータはコピーされない!
let c = clone; // strong_count = 3
内部レイアウト(ヒープ上、隣接して割り当て):
heap: [strong_count: usize(8B)] [weak_count: usize(8B)] [T: データ]
Rc<T>: ptr → T のアドレスを指す(実際には strong_count の手前を指す)
Rc::clone は strong_count を増やすだけで、データはコピーされません。最後の Rc<T> がドロップされると → strong_count = 0 → データが解放されます。Rc は Send を実装していません——その参照カウント操作はアトミックではなく、スレッド間で使用するとデータ競合を引き起こします。
Arc: マルチスレッドの参照カウント
use Arc;
let a = new;
let b = clone; // 原子インクリメント → スレッドセーフ
spawn;
内部構造は Rc と同じですが、strong/weak カウントの操作は atomic 命令を使用します。Arc::clone は Rc::clone より約 10 倍遅い(アトミックインクリメント vs 非アトミックインクリメント)ですが、ヒープ割り当てにとっては依然として非常に高速です(約 10ns vs 約 1ns)。
Weak: 循環参照の防止
use ;
let strong = new;
let weak: = downgrade; // strong_count は増加しない
drop; // strong_count = 0 → データ解放
assert!; // weak はデータが解放されたことを検知 → None
古典的な用途:木構造において、親ノードが子ノードを指す Weak 参照を保持します。もし Rc を保持すると、循環参照になります——親が子を参照し、子が親を参照し、どちらも解放できません。Weak はデータの解放を防ぐものではなく、upgrade() メソッドを通じて一時的にデータへのアクセス権を取得します(None を返す可能性があります)。
参考
- Rust Book: 第15章 15.1〜15.4
- Rustonomicon: Arc の原子性、参照カウント
Keywords: Box, Rc, Arc, Weak, 参照カウント, ヒープ割り当て, 再帰型, DST, Drop, 原子性