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ポインタを作ります。 |
サンプルコード
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 — Weak参照で循環参照を防ぐ
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して一時的に強参照を取ります。
if let Some(p) = child.borrow().parent.as_ref().and_then(|w| w.upgrade()) {
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 — Arc + join でスレッド結果を集計する
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
概要
Rustの所有権システムでは通常1つの値に所有者は1人だけですが、『Rc<T>』や『Arc<T>』を使うと複数の所有者を持つことができます。すべての参照がなくなったとき(カウントが0になったとき)にメモリが自動的に解放されます。
『Rc』はスレッド間で共有できません(『Send』トレイトを実装していないため)。『Send』トレイトはスレッド間で値を安全に送信できることを示すマーカーです。スレッド間共有には必ず『Arc』を使用してください。
Rcを使った循環参照はメモリリークを引き起こします。循環参照を作る可能性がある場合は、片方を『Weak<T>』(弱参照)にしてカウントが循環しないようにする必要があります。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。