第15章: スマートポインタ (Smart Pointers)

学習目標

  • スマートポインタの概念を理解する
  • Box、Rc、Arc の使い分けを学ぶ
  • RefCell と内部可変性を理解する
  • 循環参照の問題と Weak を学ぶ
  • ---

    15.1 スマートポインタとは

    15.1.1 基本概念

    スマートポインタは、ポインタのように振る舞うが、追加のメタデータと機能を持つデータ構造です。

    ┌─────────────────────────────────────────────────────────┐
    │          通常の参照 vs スマートポインタ                  │
    ├─────────────────────────────────────────────────────────┤
    │                                                         │
    │  【通常の参照 &T】                                       │
    │  - データを借用するだけ                                  │
    │  - 所有権なし                                            │
    │  - メタデータなし                                        │
    │                                                         │
    │  【スマートポインタ】                                    │
    │  - データを所有する                                      │
    │  - Deref/Drop トレイトを実装                             │
    │  - 追加機能(参照カウント、内部可変性等)                │
    │                                                         │
    └─────────────────────────────────────────────────────────┘
    

    15.1.2 主要なスマートポインタ

    Box<T>      - ヒープ割り当て
    Rc<T>       - 参照カウント(シングルスレッド)
    Arc<T>      - 参照カウント(マルチスレッド)
    RefCell<T>  - 内部可変性
    Weak<T>     - 弱い参照
    

    ---

    15.2 Box - ヒープ割り当て

    15.2.1 基本的な使い方

    fn main() {
        // スタックに割り当て
        let x = 5;
    
        // ヒープに割り当て
        let y = Box::new(5);
    
        println!("x: {}, y: {}", x, *y);
    }
    

    15.2.2 Boxが必要な場面

    1. サイズが未知の型

    // 再帰的な型(Boxなしではコンパイルエラー)
    enum List {
        Cons(i32, Box<List>),
        Nil,
    }
    
    use List::{Cons, Nil};
    
    fn main() {
        let list = Cons(1,
            Box::new(Cons(2,
                Box::new(Cons(3,
                    Box::new(Nil))))));
    
        println!("{:?}", list);
    }
    

    なぜBoxが必要か?

    Boxなし:
    ┌─────────────────────────────────────────────────────────┐
    │  enum List {                                            │
    │      Cons(i32, List),  // サイズが無限大!               │
    │      Nil,                                               │
    │  }                                                      │
    │                                                         │
    │  List のサイズ = i32 + List = i32 + (i32 + List) = ∞   │
    └─────────────────────────────────────────────────────────┘
    
    Boxあり:
    ┌─────────────────────────────────────────────────────────┐
    │  enum List {                                            │
    │      Cons(i32, Box<List>),  // サイズが確定!            │
    │      Nil,                                               │
    │  }                                                      │
    │                                                         │
    │  List のサイズ = max(i32 + usize, 0) = 16バイト         │
    └─────────────────────────────────────────────────────────┘
    

    2. 大きなデータの移動回避

    fn main() {
        // 大きな配列
        let large_array = [0; 10000];
    
        // スタック上で移動(コピー)
        let moved = large_array;  // 遅い!
    
        // ヒープ上のポインタのみ移動
        let boxed = Box::new([0; 10000]);
        let moved_box = boxed;  // 高速!(8バイトのポインタのみコピー)
    }
    

    3. トレイトオブジェクト

    trait Draw {
        fn draw(&self);
    }
    
    struct Circle;
    struct Square;
    
    impl Draw for Circle {
        fn draw(&self) {
            println!("Drawing circle");
        }
    }
    
    impl Draw for Square {
        fn draw(&self) {
            println!("Drawing square");
        }
    }
    
    fn main() {
        // 異なる型を同じVecに格納
        let shapes: Vec<Box<dyn Draw>> = vec![
            Box::new(Circle),
            Box::new(Square),
        ];
    
        for shape in shapes {
            shape.draw();
        }
    }
    

    ---

    15.3 Rc - 参照カウント

    15.3.1 基本概念

    Rc (Reference Counted) は、複数の所有者を許可します(シングルスレッド専用)。

    use std::rc::Rc;
    
    fn main() {
        let a = Rc::new(5);
        println!("Count: {}", Rc::strong_count(&a));  // 1
    
        let b = Rc::clone(&a);
        println!("Count: {}", Rc::strong_count(&a));  // 2
    
        {
            let c = Rc::clone(&a);
            println!("Count: {}", Rc::strong_count(&a));  // 3
        }
    
        println!("Count: {}", Rc::strong_count(&a));  // 2
        println!("Value: {}", a);  // 5
    }
    

    15.3.2 グラフ構造の例

    use std::rc::Rc;
    
    #[derive(Debug)]
    enum List {
        Cons(i32, Rc<List>),
        Nil,
    }
    
    use List::{Cons, Nil};
    
    fn main() {
        let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
        println!("Count after a: {}", Rc::strong_count(&a));  // 1
    
        let b = Cons(3, Rc::clone(&a));
        println!("Count after b: {}", Rc::strong_count(&a));  // 2
    
        {
            let c = Cons(4, Rc::clone(&a));
            println!("Count after c: {}", Rc::strong_count(&a));  // 3
        }
    
        println!("Count after c drop: {}", Rc::strong_count(&a));  // 2
    }
    

    メモリレイアウト:

    b = Cons(3, Rc) ──┐
                      ├──> a = Cons(5, Rc) ──> Cons(10, Rc) ──> Nil
    c = Cons(4, Rc) ──┘
    
    参照カウント: 2
    

    15.3.3 Rcの制限

    use std::rc::Rc;
    
    fn main() {
        let a = Rc::new(5);
    
        // エラー!Rcは不変借用のみ
        // *a = 10;  // cannot assign to data in an `Rc`
    }
    

    ---

    15.4 RefCell - 内部可変性

    15.4.1 内部可変性パターン

    RefCell は、不変な値の内部で可変性を提供します(実行時借用チェック)。

    ┌─────────────────────────────────────────────────────────┐
    │          借用規則のチェックタイミング                    │
    ├─────────────────────────────────────────────────────────┤
    │                                                         │
    │  【通常の参照】                                          │
    │  - コンパイル時にチェック                                │
    │  - 違反するとコンパイルエラー                            │
    │                                                         │
    │  【RefCell<T>】                                          │
    │  - 実行時にチェック                                      │
    │  - 違反するとパニック                                    │
    │                                                         │
    └─────────────────────────────────────────────────────────┘
    

    15.4.2 基本的な使い方

    use std::cell::RefCell;
    
    fn main() {
        let x = RefCell::new(5);
    
        // 不変借用
        {
            let r1 = x.borrow();
            let r2 = x.borrow();
            println!("{} {}", r1, r2);
        }
    
        // 可変借用
        {
            let mut r = x.borrow_mut();
            *r += 1;
        }
    
        println!("{}", x.borrow());  // 6
    }
    

    15.4.3 実行時エラーの例

    use std::cell::RefCell;
    
    fn main() {
        let x = RefCell::new(5);
    
        let r1 = x.borrow();
        // let r2 = x.borrow_mut();  // パニック!
        // already borrowed: BorrowMutError
    }
    

    15.4.4 Rc> パターン

    複数の所有者と可変性を組み合わせる:

    use std::cell::RefCell;
    use std::rc::Rc;
    
    #[derive(Debug)]
    struct Node {
        value: i32,
        children: Vec<Rc<RefCell<Node>>>,
    }
    
    impl Node {
        fn new(value: i32) -> Rc<RefCell<Self>> {
            Rc::new(RefCell::new(Node {
                value,
                children: vec![],
            }))
        }
    
        fn add_child(parent: &Rc<RefCell<Self>>, child: Rc<RefCell<Self>>) {
            parent.borrow_mut().children.push(child);
        }
    }
    
    fn main() {
        let root = Node::new(1);
        let child1 = Node::new(2);
        let child2 = Node::new(3);
    
        Node::add_child(&root, child1);
        Node::add_child(&root, child2);
    
        println!("Root has {} children", root.borrow().children.len());
    }
    

    ---

    15.5 Arc - スレッドセーフな参照カウント

    15.5.1 基本的な使い方

    Arc (Atomically Reference Counted) は、マルチスレッドで使える Rcです。

    use std::sync::Arc;
    use std::thread;
    
    fn main() {
        let data = Arc::new(vec![1, 2, 3]);
    
        let mut handles = vec![];
    
        for i in 0..3 {
            let data = Arc::clone(&data);
            let handle = thread::spawn(move || {
                println!("Thread {}: {:?}", i, data);
            });
            handles.push(handle);
        }
    
        for handle in handles {
            handle.join().unwrap();
        }
    }
    

    15.5.2 Arc> パターン

    スレッド間で可変データを共有:

    use std::sync::{Arc, Mutex};
    use std::thread;
    
    fn main() {
        let counter = Arc::new(Mutex::new(0));
        let mut handles = vec![];
    
        for _ in 0..10 {
            let counter = Arc::clone(&counter);
            let handle = thread::spawn(move || {
                let mut num = counter.lock().unwrap();
                *num += 1;
            });
            handles.push(handle);
        }
    
        for handle in handles {
            handle.join().unwrap();
        }
    
        println!("Result: {}", *counter.lock().unwrap());  // 10
    }
    

    15.5.3 RcとArcの比較

    use std::rc::Rc;
    use std::sync::Arc;
    
    fn main() {
        // Rc: シングルスレッド、高速
        let rc = Rc::new(5);
        let rc_clone = Rc::clone(&rc);
    
        // Arc: マルチスレッド、若干遅い(アトミック操作のため)
        let arc = Arc::new(5);
        let arc_clone = Arc::clone(&arc);
    }
    

    ---

    15.6 Weak - 弱い参照

    15.6.1 循環参照の問題

    use std::cell::RefCell;
    use std::rc::Rc;
    
    #[derive(Debug)]
    struct Node {
        value: i32,
        next: Option<Rc<RefCell<Node>>>,
    }
    
    fn main() {
        let a = Rc::new(RefCell::new(Node { value: 1, next: None }));
        let b = Rc::new(RefCell::new(Node { value: 2, next: Some(Rc::clone(&a)) }));
    
        // 循環参照を作成
        a.borrow_mut().next = Some(Rc::clone(&b));
    
        // メモリリークが発生!
        // a -> b -> a -> b -> ...
        println!("a count: {}", Rc::strong_count(&a));  // 2
        println!("b count: {}", Rc::strong_count(&b));  // 2
    }
    

    15.6.2 Weakで解決

    use std::cell::RefCell;
    use std::rc::{Rc, Weak};
    
    #[derive(Debug)]
    struct Node {
        value: i32,
        parent: RefCell<Weak<Node>>,
        children: RefCell<Vec<Rc<Node>>>,
    }
    
    impl Node {
        fn new(value: i32) -> Rc<Self> {
            Rc::new(Node {
                value,
                parent: RefCell::new(Weak::new()),
                children: RefCell::new(vec![]),
            })
        }
    }
    
    fn main() {
        let leaf = Node::new(3);
    
        println!(
            "leaf strong = {}, weak = {}",
            Rc::strong_count(&leaf),
            Rc::weak_count(&leaf),
        );  // strong = 1, weak = 0
    
        {
            let branch = Node::new(5);
            *leaf.parent.borrow_mut() = Rc::downgrade(&branch);
            branch.children.borrow_mut().push(Rc::clone(&leaf));
    
            println!(
                "branch strong = {}, weak = {}",
                Rc::strong_count(&branch),
                Rc::weak_count(&branch),
            );  // strong = 1, weak = 1
    
            println!(
                "leaf strong = {}, weak = {}",
                Rc::strong_count(&leaf),
                Rc::weak_count(&leaf),
            );  // strong = 2, weak = 0
        }
    
        // branchがドロップされた
        println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());  // None
    }
    

    ---

    15.7 実践例:ツリー構造

    15.7.1 二分探索木

    use std::cell::RefCell;
    use std::rc::Rc;
    
    type Link = Option<Rc<RefCell<Node>>>;
    
    #[derive(Debug)]
    struct Node {
        value: i32,
        left: Link,
        right: Link,
    }
    
    impl Node {
        fn new(value: i32) -> Rc<RefCell<Self>> {
            Rc::new(RefCell::new(Node {
                value,
                left: None,
                right: None,
            }))
        }
    
        fn insert(root: &Rc<RefCell<Self>>, value: i32) {
            let mut node = root.borrow_mut();
            if value < node.value {
                match node.left {
                    Some(ref left) => Self::insert(left, value),
                    None => node.left = Some(Node::new(value)),
                }
            } else {
                match node.right {
                    Some(ref right) => Self::insert(right, value),
                    None => node.right = Some(Node::new(value)),
                }
            }
        }
    
        fn traverse(node: &Link) {
            if let Some(ref n) = node {
                let n = n.borrow();
                Self::traverse(&n.left);
                println!("{}", n.value);
                Self::traverse(&n.right);
            }
        }
    }
    
    fn main() {
        let root = Node::new(5);
        Node::insert(&root, 3);
        Node::insert(&root, 7);
        Node::insert(&root, 1);
        Node::insert(&root, 9);
    
        Node::traverse(&Some(root));
        // 出力: 1, 3, 5, 7, 9
    }
    

    15.7.2 グラフ構造

    use std::cell::RefCell;
    use std::rc::Rc;
    
    #[derive(Debug)]
    struct Graph {
        nodes: Vec<Rc<RefCell<GraphNode>>>,
    }
    
    #[derive(Debug)]
    struct GraphNode {
        value: i32,
        neighbors: Vec<Rc<RefCell<GraphNode>>>,
    }
    
    impl Graph {
        fn new() -> Self {
            Graph { nodes: vec![] }
        }
    
        fn add_node(&mut self, value: i32) -> Rc<RefCell<GraphNode>> {
            let node = Rc::new(RefCell::new(GraphNode {
                value,
                neighbors: vec![],
            }));
            self.nodes.push(Rc::clone(&node));
            node
        }
    
        fn add_edge(
            from: &Rc<RefCell<GraphNode>>,
            to: &Rc<RefCell<GraphNode>>,
        ) {
            from.borrow_mut().neighbors.push(Rc::clone(to));
        }
    }
    
    fn main() {
        let mut graph = Graph::new();
    
        let n1 = graph.add_node(1);
        let n2 = graph.add_node(2);
        let n3 = graph.add_node(3);
    
        Graph::add_edge(&n1, &n2);
        Graph::add_edge(&n1, &n3);
        Graph::add_edge(&n2, &n3);
    
        println!("Node 1 has {} neighbors", n1.borrow().neighbors.len());
    }
    

    ---

    15.8 まとめ

    スマートポインタの選択

    ┌─────────────────────────────────────────────────────────┐
    │          スマートポインタの使い分け                      │
    ├─────────────────────────────────────────────────────────┤
    │                                                         │
    │  Box<T>                                                 │
    │  └─ ヒープ割り当て、単一所有者                          │
    │                                                         │
    │  Rc<T>                                                  │
    │  └─ 複数所有者(シングルスレッド)                       │
    │                                                         │
    │  Arc<T>                                                 │
    │  └─ 複数所有者(マルチスレッド)                         │
    │                                                         │
    │  RefCell<T>                                             │
    │  └─ 内部可変性(実行時借用チェック)                     │
    │                                                         │
    │  Rc<RefCell<T>>                                         │
    │  └─ 複数所有者 + 内部可変性                              │
    │                                                         │
    │  Arc<Mutex<T>>                                          │
    │  └─ スレッドセーフな共有可変データ                       │
    │                                                         │
    └─────────────────────────────────────────────────────────┘
    

    次のステップ

    次の章では、並行処理について学びます。スレッド、Send/Syncトレイト、データ競合の防止方法を理解します。

    ---

    参考資料

  • The Rust Book: Smart Pointers
  • Rc documentation
  • RefCell documentation