このページの目次

Box, Rc, Arc

Box は単一所有者のヒープ割り当て(最もシンプル)、Rc はシングルスレッドの参照カウント(共有所有権)、Arc はマルチスレッドの原子参照カウントです。Weak は循環参照を解消し、メモリレイアウト上、Rc/Arc の値と参照カウントはヒープ上で隣接して割り当てられます——これら3つを理解することは、Rust の「GC は使わないが参照カウントがある」という仕組みを理解することに他なりません。

Box: 単一所有者のヒープ割り当て

let b = Box::new(42);               // ヒープ上に i32 を割り当てる
// stack: [8バイトのポインタ] → heap: [4バイトの値: 42]

Box<T> は Rust で最もシンプルなヒープ割り当てです——単一所有者であり、参照カウントを持ちません。b がスコープを外れると、ヒープメモリは解放されます。C++ の std::unique_ptr<T> に似ていますが、Rust のバージョンではムーブセマンティクスのキーワードは不要です——所有権はコンパイル時に保証されます。

Box の使用タイミング

  1. 再帰型⁠: Rust では型サイズをコンパイル時に知らなければなりません。enum List { Cons(i32, List), Nil } とすると再帰によりサイズが無限大になります。解決策:Cons(i32, Box<List>)——Box のサイズは固定(ポインタ、8バイト)であり、再帰はヒープ経由で間接的に解決されます
  2. トレイトオブジェクト⁠: Box<dyn Display> は、Display を実装した任意の型を格納できます
  3. 大きな値をスタックからヒープへ⁠: [u8; 1000000] のような大きな値がある場合、Box::new([0u8; 1000000]) を使うと、スタック上には 8 バイトのポインタしか置かれなくなります
  4. DST (動的サイズ型): Box<[T]>, Box<str>, Box<dyn Trait>

Rc: シングルスレッドの参照カウント

use std::rc::Rc;
let a = Rc::new(vec![1, 2, 3]);     // strong_count = 1
let b = Rc::clone(&a);              // strong_count = 2、Vec のデータはコピーされない!
let c = Rc::clone(&a);              // 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 std::sync::Arc;
let a = Arc::new(vec![1, 2, 3]);
let b = Arc::clone(&a);            // 原子インクリメント → スレッドセーフ
std::thread::spawn(move || {
    println!("{:?}", b);           // b はスレッド内にムーブ可能
});

内部構造は Rc と同じですが、strong/weak カウントの操作は atomic 命令を使用します。Arc::cloneRc::clone より約 10 倍遅い(アトミックインクリメント vs 非アトミックインクリメント)ですが、ヒープ割り当てにとっては依然として非常に高速です(約 10ns vs 約 1ns)。

Weak: 循環参照の防止

use std::rc::{Rc, Weak};
let strong = Rc::new(42);
let weak: Weak<_> = Rc::downgrade(&strong);  // strong_count は増加しない

drop(strong);                        // strong_count = 0 → データ解放
assert!(weak.upgrade().is_none());   // weak はデータが解放されたことを検知 → None

古典的な用途:木構造において、親ノードが子ノードを指す Weak 参照を保持します。もし Rc を保持すると、循環参照になります——親が子を参照し、子が親を参照し、どちらも解放できません。Weak はデータの解放を防ぐものではなく、upgrade() メソッドを通じて一時的にデータへのアクセス権を取得します(None を返す可能性があります)。

参考

  • Rust Book: 第15章 15.1〜15.4
  • Rustonomicon: Arc の原子性、参照カウント

Keywords: Box, Rc, Arc, Weak, 参照カウント, ヒープ割り当て, 再帰型, DST, Drop, 原子性