導入
所有権の大原則は「所有者は1人だけ」でした。しかし現実には「1つのデータを複数の場所で共有したい」ことがあります。たとえば、複数のリストが末尾を共有するグラフ構造。この「共有所有」を可能にするのが Rc<T>(Reference Counted=参照カウント)です。
説明
Rc<T> は、同じデータを指す所有者が何人いるかを数え(参照カウント)、最後の1人がいなくなったときに解放します。
use std::rc::Rc;
fn main() {
let a = Rc::new(String::from("shared")); // 参照カウント = 1
println!("count = {}", Rc::strong_count(&a)); // 1
let b = Rc::clone(&a); // 所有者が増える。データは複製せず、カウントを +1
println!("count = {}", Rc::strong_count(&a)); // 2
{
let c = Rc::clone(&a); // カウント +1
println!("count = {}", Rc::strong_count(&a)); // 3
} // c がスコープを抜ける → カウント -1
println!("count = {}", Rc::strong_count(&a)); // 2
// a と b はどちらも同じ "shared" を指している
println!("{a} {b}");
} // a, b が消え、カウントが 0 になった瞬間にヒープが解放される
Rc::new(値)… 参照カウント付きでヒープに確保。カウントは1から。Rc::clone(&a)… データは複製しません。ポインタを共有し、カウントを+1するだけ(だから軽い)。第4章のString::clone(実データをコピー)とは意味が違うので注意。- 所有者(
Rc)がスコープを抜けるたびカウントが-1され、0になった瞬間にデータが解放されます。
graph LR
subgraph stack["スタック(3人の所有者)"]
a["a"]
b["b"]
c["c"]
end
subgraph heap["ヒープ(カウント付き)"]
d["'shared'(count = 3)"]
end
a --> d
b --> d
c --> d
Rc のおかげで「1つのデータを複数から共有し、全員が使い終わったら自動で解放」が安全にできます。これも実行時のGCではなく、カウントの増減という軽い仕組みで実現しています。
注意:Rc<T> は不変の共有です(読むだけ)。しかも Rc はシングルスレッド専用です(マルチスレッドで共有するには、スレッド安全版の Arc を使います——後述)。「共有しつつ書き換えたい」場合は、次の RefCell と組み合わせます。
試すには
Rc::clone(&a) はデータをコピーせずカウントを増やすだけ、という点が肝です。Rc::strong_count でカウントの増減を追うと、「最後の所有者が消えたら解放」という仕組みが目で見えます。共有所有が必要な木・グラフ構造でよく使います。