言語
日本語
English

Caution

お使いのブラウザはJavaScriptが無効になっております。
当サイトでは検索などの処理にJavaScriptを使用しています。
より快適にご利用頂くため、JavaScriptを有効にしたうえで当サイトを閲覧することをお勧めいたします。

Rust辞典

  1. トップページ
  2. Rust辞典
  3. Rc<T> / Arc<T>

Rc<T> / Arc<T>

対応: Rust 1.0(2015)

『Rc<T>』と『Arc<T>』は参照カウント方式のスマートポインタで、1つの値を複数の場所から所有できるようにします。『Rc』はシングルスレッド用、『Arc』はマルチスレッド対応版です。

構文

use std::rc::Rc;
use std::sync::Arc;

// Rc:シングルスレッドで複数の所有者を作ります。
let a = Rc::new(値);
let b = Rc::clone(&a); // 参照カウントが増えます。

// Arc:マルチスレッドで複数の所有者を作ります。
let a = Arc::new(値);
let b = Arc::clone(&a); // スレッドをまたいで共有できます。

// 参照カウントの確認
Rc::strong_count(&a)
Arc::strong_count(&a)

メソッド一覧

メソッド概要
Rc::new(v)値vをRcでラップします。参照カウントは1になります。
Rc::clone(&a)参照カウントを増やしながら新しいRcポインタを作ります。
Rc::strong_count(&a)現在の強参照カウントを返します。
Rc::weak_count(&a)現在の弱参照カウントを返します(Weak参照用)。
Arc::new(v)値vをArcでラップします。スレッドをまたいで安全に共有できます。
Arc::clone(&a)参照カウントをアトミックに増やしながら新しいArcポインタを作ります。

サンプルコード

参照カウント型スマートポインタ『Rc』と、スレッドセーフ版の『Arc』の基本的な使い方を確認するサンプルコードです。

sample_rc_arc.rs
use std::rc::Rc;
use std::sync::Arc;
use std::thread;

fn main() {
    // Rc:シングルスレッドで複数所有します。
    let rc_val = Rc::new(String::from("shared"));
    let rc_a = Rc::clone(&rc_val);
    let rc_b = Rc::clone(&rc_val);

    println!("{}", rc_val); // shared
    println!("{}", rc_a); // shared
    println!("count: {}", Rc::strong_count(&rc_val)); // count: 3

    drop(rc_a);
    println!("after drop: {}", Rc::strong_count(&rc_val)); // after drop: 2

    // Arc:スレッド間で共有します。
    let arc_val = Arc::new(42);
    let handles: Vec<_> = (0..3).map(|i| {
        let val = Arc::clone(&arc_val);
        thread::spawn(move || {
            println!("thread {}: {}", i, val);
        })
    }).collect();

    for h in handles {
        h.join().unwrap();
    }
}

コンパイルして実行すると次のように出力されます。

rustc rc_arc.rs
./rc_arc
shared
shared
count: 3
after drop: 2
thread 0: 42
thread 1: 42
thread 2: 42
rc_arc2.rs
use std::rc::{Rc, Weak};
use std::cell::RefCell;

struct Node {
    name: String,
    child: Option<Rc<RefCell<Node>>>,
    parent: Option<Weak<RefCell<Node>>>, // 循環参照を防ぐためWeakを使います。
}

impl Node {
    fn new(name: &str) -> Rc<RefCell<Node>> {
        Rc::new(RefCell::new(Node {
            name: name.to_string(),
            child: None,
            parent: None,
        }))
    }
}

fn main() {
    let parent = Node::new("Goku");
    let child = Node::new("Gohan");

    // 子ノードの parent に弱参照をセットします。
    child.borrow_mut().parent = Some(Rc::downgrade(&parent));
    // 親ノードの child に強参照をセットします。
    parent.borrow_mut().child = Some(Rc::clone(&child));

    println!("parent count: {}", Rc::strong_count(&parent)); // 1
    println!("child count:  {}", Rc::strong_count(&child)); // 2(parent が所有)

    // Weakからupgradeして一時的に強参照を取ります。
    let parent_ref = child.borrow().parent.as_ref().and_then(|w| w.upgrade());
    if let Some(p) = parent_ref {
        println!("child's parent: {}", p.borrow().name);
    }
} // parent が解放 → child の strong_count も0になる(メモリリークなし)
rustc rc_arc2.rs
./rc_arc2
parent count: 1
child count:  2
child's parent: Goku
rc_arc3.rs
use std::sync::Arc;
use std::thread;

fn main() {
    let scores = Arc::new(vec![85, 92, 78, 96, 88]);

    let handles: Vec<_> = (0..scores.len()).map(|i| {
        let data = Arc::clone(&scores);
        thread::spawn(move || {
            println!("thread {}: score = {}", i, data[i]);
            data[i]
        })
    }).collect();

    let total: i32 = handles.into_iter().map(|h| h.join().unwrap()).sum();
    println!("total: {}", total); // total: 439
    println!("avg: {:.1}", total as f64 / scores.len() as f64); // avg: 87.8
}
rustc rc_arc3.rs
./rc_arc3
thread 0: score = 85
thread 1: score = 92
thread 2: score = 78
thread 3: score = 96
thread 4: score = 88
total: 439
avg: 87.8

よくあるミス

よくあるミス1: RcをマルチスレッドでSend不可エラー

Rcは『Send』トレイトを実装していないため、スレッドに渡すとコンパイルエラーになります。

rc_arc_mistake1.rs
use std::rc::Rc;
use std::thread;

fn main() {
    let val = Rc::new(42);
    let v = Rc::clone(&val);
    // Rcはスレッド間で送れません。
    thread::spawn(move || {
        println!("{}", v);
    });
}
rustc rc_arc_mistake1.rs
error[E0277]: `Rc<i32>` cannot be sent between threads safely

スレッド間で共有する場合はArcを使います。

rc_arc_fix1.rs
use std::sync::Arc;
use std::thread;

fn main() {
    let val = Arc::new(42);
    let v = Arc::clone(&val);
    let handle = thread::spawn(move || {
        println!("{}", v);
    });
    handle.join().unwrap();
}
rustc rc_arc_fix1.rs
./rc_arc_fix1
42

よくあるミス2: Rc同士の循環参照でメモリリーク

2つのRcが互いを参照し合うと参照カウントが0にならず、メモリリークが発生します。

rc_arc_mistake2.rs
use std::rc::Rc;
use std::cell::RefCell;

struct Node {
    next: Option>,
}

fn main() {
    let a = Rc::new(RefCell::new(Node { next: None }));
    let b = Rc::new(RefCell::new(Node { next: None }));

    // a→bの参照を設定します。
    a.borrow_mut().next = Some(Rc::clone(&b));
    // b→aの参照を設定します。→ 循環参照が発生します。
    b.borrow_mut().next = Some(Rc::clone(&a));

    // スコープを抜けてもカウントが0にならないためメモリが解放されません。
    println!("a count: {}", Rc::strong_count(&a)); // 2
    println!("b count: {}", Rc::strong_count(&b)); // 2
}
rustc rc_arc_mistake2.rs
./rc_arc_mistake2
a count: 2
b count: 2

片方をWeak参照にすることで循環参照を防げます(サンプルコードのrc_arc2.rsを参照)。

よくあるミス3: .clone()とRc::clone(&a)の違い

.clone()とRc::clone(&a)はどちらも参照カウントを増やして新しいRcポインタを返す点で同じ動作です。ただしRc::clone(&a)の方がRcのcloneであることが明示的でコードを読む人に意図が伝わりやすくなります。

rc_arc_clone.rs
use std::rc::Rc;

fn main() {
    let a = Rc::new(String::from("hello"));

    // どちらも同じ動作です。参照カウントが増えます。
    let b = Rc::clone(&a); // こちらがよく使われます。
    let c = a.clone(); // こちらも動作します。

    println!("count: {}", Rc::strong_count(&a)); // 3
    println!("a={}, b={}, c={}", a, b, c);
}
rustc rc_arc_clone.rs
./rc_arc_clone
count: 3
a=hello, b=hello, c=hello

概要

Rustの所有権システムでは通常1つの値に所有者は1人だけですが、『Rc<T>』や『Arc<T>』を使うと複数の所有者を持つことができます。すべての参照がなくなったとき(カウントが0になったとき)にメモリが自動的に解放されます。

『Rc』はスレッド間で共有できません(『Send』トレイトを実装していないため)。『Send』トレイトはスレッド間で値を安全に送信できることを示すマーカーです。スレッド間共有には『Arc』を使います。

Rcを使った循環参照はメモリリークを引き起こします。循環参照を作る可能性がある場合は、片方を『Weak<T>』(弱参照)にしてカウントが循環しないようにする必要があります。

記事の間違いや著作権の侵害等ございましたらお手数ですがまでご連絡頂ければ幸いです。