🦀

Rc と Arc の違い

2024/04/22に公開

参照カウント方式のスマートポインタである std::rc::Rcstd::sync::Arc の違いについて説明します。

Rc(Reference Counted) とは

Rcはスレッド非安全な参照カウント型のスマートポインタです。データを複数の所有者で共有したい場合に使用します。しかし、単一スレッド内でのみ使用可能という制限があります。メモリ使用量は Arc よりも少なくなります。

use std::rc::Rc;

fn main() {
    let value = Rc::new(5); // 新しい参照カウント付きの値を作成

    let value_a = value.clone(); // 参照カウントが2になる
    let value_b = value.clone(); // 参照カウントが3になる

    println!("value_a: {}", value_a); // 5
    println!("value_b: {}", value_b); // 5

    // value_a と value_b が有効な間は value はドロップされない
}

この例では、Rc::new(5)で新しいRcインスタンスを作成しています。clone()メソッドで参照をコピーすると、参照カウントが増えていきます。最後の参照が無効になったときにデータが解放されます。

Arc(Atomic Reference Counted) とは

Arcはスレッド安全な参照カウント型のスマートポインタです。Rcと同様にデータを複数の所有者で共有しますが、複数のスレッド間でも安全に使用できるのが大きな違いです。その分メモリ使用量が大きくなります。

use std::sync::Arc;
use std::thread;

fn main() {
    let value = Arc::new(5);

    let value_a = value.clone();
    let value_b = value.clone();

    let thread_a = thread::spawn(move || {
        println!("Thread A: {}", value_a);
    });

    let thread_b = thread::spawn(move || {
        println!("Thread B: {}", value_b);
    });

    thread_a.join().unwrap();
    thread_b.join().unwrap();
}

この例では、Arc::new(5)で新しいArcインスタンスを作成しています。value_avalue_bは同じデータを参照しています。それぞれ別のスレッドで値にアクセスできることがポイントです。Arcならスレッド安全に値を共有できます。

メモリ使用量の違い

RcArcより少ないメモリを消費します。これはArcがスレッド安全性を実現するために追加のメタデータを持つためです。単一スレッドで使う場合はRcを使った方がメモリ効率が良くなります。

[備考] 可変アクセスについて

RcArcはどちらも不変データの共有のみをサポートしており、可変アクセスはできません。

use std::{rc::Rc, sync::Arc};

fn main() {
    let a = Rc::new([1,3,2]);
    a.sort(); // Rcが保持するデータは不変なので変更できない

    let b = Arc::new([1,3,2]);
    b.sort(); // Arcが保持するデータは不変なので変更できない

    let mut c = Rc::new([1,3,2]);
    c.sort(); // Rcが保持するデータは不変なので変更できない

    let mut d = Arc::new([1,3,2]);
    d.sort(); // Arcが保持するデータは不変なので変更できない
}

RcArcが保持するデータ自体が不変であるため、mutを付けてもデータを変更することはできません。

これは、Rustの所有権ルールに由来します。

  • 所有権は1つのスレッドにしか存在できない
  • 可変な参照も1つしか存在できない

そのため、RcArcで可変アクセスを許可すると、複数の所有者から同時にデータを書き換えようとした場合にデータ競合が発生する可能性があります。
このように、RcArcはデータの不変性を保証するためにデータの変更を許可していません。

可変アクセスが必要な場合は、内部可変性を利用する必要があります。
内部可変性を実現する主な方法は以下の3つです。

  • RefCell<T>:単一スレッド内での可変借用を実現します。スレッド安全ではありません。
  • Mutex<T>:スレッド安全な可変借用を実現します。一度に1つのスレッドのみがデータにアクセスできます。
  • RwLock<T>:複数の読み取りと1つの書き込みを同時に許可する、スレッド安全な内部可変性を実現します。

https://zenn.dev/keisuke333/articles/92530736abe71e

まとめ

  • Rcはスレッド非安全な参照カウント型のスマートポインタ
  • Arcはスレッド安全な参照カウント型のスマートポインタ
  • Rcはメモリ使用量が少ない
  • Arcはスレッド間でデータを安全に共有できる
  • 単一スレッドでデータ共有が必要ならばRcを使う
  • 複数スレッド間でデータ共有が必要ならばArcを使う
  • RcArcはどちらも不変データの共有のみをサポートしており、可変アクセスはできない

Discussion