本文へスキップ
BecomeCoder

Rustコース · 第10章 スマートポインタと並行性 · レッスン40

Rc ― 複数の所有者を持つ

ブラウザで完結

導入

所有権の大原則は「所有者は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 でカウントの増減を追うと、「最後の所有者が消えたら解放」という仕組みが目で見えます。共有所有が必要な木・グラフ構造でよく使います。

実際に動かしてみよう

下のエディタにRustを書いて「実行」を押すと、学習用シミュレータが println! の出力を表示します(本物のrustcではなく、教材の範囲を再現した軽量エンジンです)。struct・enum・match・トレイト・Vec/String/HashMap・Option/Result なども動きます。本文の例を書き換えて動かしてみましょう(所有権・借用チェッカーのコンパイルエラーは再現しないため、動きの確認用として使ってください)。

Rust — ブラウザ内で実行(学習用シミュレータ)

Rustの教材サブセットを動かす学習用シミュレータを読み込みます(本物のrustcではなく、動きを再現した軽量な自作エンジンです)。
スクロールして表示された時点でも自動で読み込まれます。