第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トレイト、データ競合の防止方法を理解します。
---