Rust のスマートポインタまとめ
Rust 勉強シリーズ。
いまここ ↓
はじめに
先日 Arc<T> や Mutex<T> に触れたので、そろそろ「いつか理解する」と放置していた ↓ の構造体について整理する。

筆者の方の補足記事 ↓
筆者の理解度は先月の時点で「Box<T> はみたことある、ほかは知らん」くらい。
スマートポインタとは
スマートポインタとは、通常の参照のように使えるだけでなく、追加のメタデータと能力を持つデータ構造のこと。
また、多くの場合スマートポインタは対象とするデータを所有している。
通常、スマートポインタは構造体で実装され、Deref トレイトと Drop トレイトを実装する。
次の記事では構造体 String や構造体 Vec もスマートポインタとして紹介されている。
Deref トレイト
Deref トレイトは参照外し演算子 ( * ) の挙動を定義できる。
次の構造体 Foo を所有する変数 foo は、参照ではないため *foo は当然コンパイルできない。
#[derive(Debug)]
struct Foo {
value: i32
}
fn main() {
let foo = Foo { value: 42 };
println!("{:?}", foo); // Foo { value: 42 }
println!("{:?}", *foo); // type `Foo` cannot be dereferenced
}
構造体 Foo が Deref トレイトを実装している場合、参照外し演算子 ( * ) により次の実装例では所有している 42 が得られる。
use std::ops::Deref;
#[derive(Debug)]
struct Foo {
value: i32
}
impl Deref for Foo {
type Target = i32;
fn deref(&self) -> &Self::Target {
&self.value
}
}
fn main() {
let foo = Foo { value: 42 };
println!("{:?}", foo); // Foo { value: 42 }
println!("{:?}", *foo); // 42
}
上記コードの *foo は *(foo.deref()) に相当する。
また Rust には参照外し型強制という変換が組み込まれており、Deref トレイトを実装した構造体の参照を参照外し後の型の参照に変換する。
この変換は参照を関数やメソッドの引数に渡すときに型が一致しないと自動的に行われる。
次のコードでは &i32 を期待するところに &foo を渡せている。
use std::ops::Deref;
#[derive(Debug)]
struct Foo {
value: i32
}
impl Deref for Foo {
type Target = i32;
fn deref(&self) -> &Self::Target {
&self.value
}
}
fn is_even(n: &i32) -> bool {
n % 2 == 0
}
fn main() {
let foo = Foo { value: 42 };
let b = is_even(&foo);
println!("{}", b); // true
}
構造体 String を引数 &str に渡せるのも Deref トレイトによるため。
fn hello(name: &str) {
println!("hello {}", name);
}
fn main() {
let name: String = String::from("John");
hello(&name); // hello John
}
構造体 String が Deref トレイトを Target = str で実装している。
impl Deref for String {
type Target = str;
fn deref(&self) -> &str {
...
}
}
Drop トレイト
Drop トレイトは値がスコープを抜けるときの挙動を定義できる。
次の構造体 Foo を所有する変数 foo は、Drop トレイトの実装によりスコープから抜けるときメッセージを出力する。
#[derive(Debug)]
struct Foo {
value: i32,
}
impl Drop for Foo {
fn drop(&mut self) {
println!("drop: {:?}", self);
}
}
fn main() {
println!("scope start"); // scope start
{
let foo = Foo { value: 42 };
} // drop: Foo { value: 42 }
println!("scope end"); // drop: end
}
Drop トレイトにより、リソースの解放やロックの解除を漏らさず実行できるようになる。
シングルスレッド用スマートポインタ
シングルスレッドで用いるスマートポインタを整理する。
ここで挙げる構造体はマルチスレッドでは使えない。
( 理由はマルチスレッドの項で解説 )
マルチスレッドでは使えないかわりに、スレッドセーフにするための実行コストを持たない。
Box<T>
Rust において値はスタック [1] に割り当てられるが、構造体 Box を使うとヒープ [2] に割り当てられる。
次のコードの変数 boxed_n はスタックに配置され、ヒープに展開した値を所有している。
fn main() {
let n = 42;
let boxed_n = Box::new(42);
}

ところで構造体 String と構造体 Box はよく似ている。
スタックにはサイズが固定で既知のものしか配置できないが、文字列は実行しないとサイズがわからない。
その問題を解決するために、構造体 String はヒープに展開された文字列を所有するスマートポインタになっている。
fn main() {
let sting1 = String::from("Lorem ipsum dolor sit amet");
let sting2 = String::from("Hello World");
}

スマートポインタ自体のサイズはコンパイル時にわかる [3] ため、スタックに配置できる。
同様に構造体 Box を使うと実行するまでサイズのわからないものを固定サイズのものとして扱える。
これは再帰的なデータ構造を実装したり、トレイトオブジェクトのために必要になる。
( 詳細は本記事の範囲外とする )
Rc<T>
構造体 Rc は複製してもコピーが行われず、かわりに所有する値への別の参照が作成される。
RC は Reference Counting を意味し、構造体 Rc は自身が所有する値への参照がいくつあるかを管理している。
カウントは複製 ( clone ) すると増加し、破棄 ( drop ) すると減少する。
同じ値を複数の値が所有するグラフのようなデータ構造で用いられる。
use std::rc::Rc;
fn main() {
let rc1: Rc<String> = Rc::new(String::from("foo"));
println!("refs: {}", Rc::strong_count(&rc1)); // refs: 1
{
let rc2: Rc<String> = rc1.clone();
println!("refs: {}", Rc::strong_count(&rc1)); // refs: 2
println!("refs: {}", Rc::strong_count(&rc2)); // refs: 2
}
println!("refs: {}", Rc::strong_count(&rc1)); // refs: 1
println!("{:?}", rc1.len()); // 3
}
Deref トレイトにより Rc<String> を String のように扱えるため、上記 rc1.len() のように所有する値のメソッドを明示的な変換なしに実行できる。
drop メソッドは参照の数をひとつ減らし、どこからも参照されなくなったら所有している値を破棄する。
unsafe impl<#[may_dangle] T: ?Sized, A: Allocator> Drop for Rc<T, A> {
fn drop(&mut self) {
unsafe {
self.inner().dec_strong(); // decrement ( 筆者注釈 )
if self.inner().strong() == 0 {
// destroy the contained object
...
}
}
}
}
したがって次のコードの構造体 Foo の破棄は、Rc<Foo> への参照がすべてなくなるタイミング一度だけ行われる。
use std::rc::Rc;
#[derive(Debug)]
struct Foo {
value: i32,
}
impl Drop for Foo {
fn drop(&mut self) {
println!("drop: {:?}", self);
}
}
fn main() {
{
let rc1: Rc<Foo> = Rc::new(Foo { value: 42 });
{
let rc2: Rc<Foo> = rc1.clone();
println!("message 1"); // message 1
// drop rc2
}
println!("message 2"); // message 2
// drop rc1
// drop: Foo { value: 42 }
}
println!("message 3"); // message 3
}
Cell<T>
構造体 Cell は mut なしに内部で所有している値を変更できる。
このような特性を内部可変性 ( Interior Mutability ) と呼ぶ。[4] [5]
use std::cell::Cell;
fn main() {
let c: Cell<i32> = Cell::new(42);
println!("{:?}", c); // Cell { value: 42 }
c.set(c.get() * 2);
println!("{:?}", c); // Cell { value: 84 }
c.replace(42);
println!("{:?}", c); // Cell { value: 42 }
}
unsafe と聞くと心配になるが、構造体 Cell は次のような仕組みで通常の借用ルールに則り安全を保証してくれている。
まず Cell<T> から &T を得る方法がないため、replace メソッドが unsafe の中で &mut T を扱っても &T と &mut T が同時に存在することはない。
また &mut T を得られる get_mut メソッドを呼ぶには Cell<T> 自体を可変参照にする必要があり、同一の Cell<T> から複数の &mut T を得ることはできない。
構造体 Cell の get メソッドは T: Copy を返却する。
したがって構造体 String のようなコピーできない値を扱うことは実質できない。
impl<T: Copy> Cell<T> {
pub fn get(&self) -> T {
...
}
}
RefCell<T>
構造体 Cell がコピーできない値を扱えないため、必要に応じて構造体 RefCell を使う。
構造体 RefCell は構造体 Cell の上位互換だが、借用ルールの検査はコンパイル時から実行時になる。
RefCell<T> から borrow メソッドで Ref<T> が得られる。
use std::cell::{RefCell, Ref};
fn main() {
let rc: RefCell<String> = RefCell::new(String::from("foo"));
let r: Ref<String> = rc.borrow();
println!("{}", r); // foo
println!("{}", r.len()); // 3
}
また borrow_mut メソッドでは RefMut<T> が得られ、これは更新が行える。
次のコードの不変の変数 rc から得られた RefMut<String> は、&mut self を必要とする String::push メソッドを実行できる。
use std::cell::RefCell;
fn main() {
let rc: RefCell<String> = RefCell::new(String::from("foo"));
rc.borrow_mut().push('!');
println!("{:?}", rc); // foo!
}
明示的な変換なしに &mut String が得られるのは、構造体 RefMut が DerefMut トレイトを実装しているため。

構造体 Box などと異なり、構造体 RefCell は借用ルールを破るとコンパイルエラーではなく実行時エラーになる。
たとえば不変参照と可変参照を同時に存在させようとすると panic が発生する。
use std::cell::{Ref, RefCell, RefMut};
fn main() {
let rc: RefCell<String> = RefCell::new(String::from("foo"));
let mut rm: RefMut<String> = rc.borrow_mut();
let r: Ref<String> = rc.borrow();
// ↑ already mutably borrowed: BorrowError
}
use std::cell::{Ref, RefCell, RefMut};
fn main() {
let rc: RefCell<String> = RefCell::new(String::from("foo"));
let r: Ref<String> = rc.borrow();
let mut rm: RefMut<String> = rc.borrow_mut();
// ↑ already borrowed: BorrowMutError
}
構造体 RefCell の活用例のひとつとして、The Rust Programming Language の通知モックの例が参考になる。 [6]
マルチスレッド用スマートポインタ
マルチスレッドで用いるスマートポインタを整理する。
まずスレッドを作成するコードを確認し、次になぜここで挙げる構造体はマルチスレッドで使用できるのか整理する。
スレッドを作る
スレッドは thread::spawn 関数で作成する。
完了を待つには構造体 JoinHandle の join メソッドを使う。
use std::thread;
fn main() {
let t1 = thread::spawn(|| {
println!("thread 1");
});
let t2 = thread::spawn(|| {
println!("thread 2");
});
t1.join().unwrap();
t2.join().unwrap();
// thread 1
// thread 2
}
変数 value を参照で捕捉するクロージャは、thread::spawn メソッドに渡せない。
use std::thread;
fn main() {
let value = 42;
let handle1 = thread::spawn(|| {
println!("thread 1: {}", value);
});
handle1.join().unwrap();
}
// error[E0373]: closure may outlive the current function,
// but it borrows `value`, which is owned by the current function
クロージャは可能な限り変数 value を &T として捕捉しようとするが、thread::spawn メソッドの型定義は F: Send + 'static になっておりクロージャが参照を含まないことを要求している。
pub fn spawn<F, T>(f: F) -> JoinHandle<T>
where
F: FnOnce() -> T,
F: Send + 'static,
T: Send + 'static,
{
...
}
コンパイルするには変数 value の所有権を明示的にクロージャに move する必要がある。
use std::thread;
fn main() {
let value = 42;
let handle1 = thread::spawn(move || {
println!("thread 1: {}", value);
});
handle1.join().unwrap();
// thread 1: 42
}
クロージャが行う変数の捕捉や Fn と FnMut と FnOnce の違いについては、整理済みのため本記事では割愛する。
Sync トレイトと Send トレイト
Sync トレイトは、複数のスレッドから参照されても安全であることを示すマーカートレイト。
Send トレイトは、所有権をスレッド間で転送できることを示すマーカートレイト。
ほとんどの型は Sync トレイトと Send トレイトを実装している ( 以降 Sync + Send とする ) が、構造体 Rc など一部の型は例外である。
thread::spawn メソッドは F も T も Send トレイトが要求されているため、次のように Rc<i32> などを扱うことはできない。
use std::rc::Rc;
use std::thread;
fn main() {
let value = Rc::new(42);
let handle1 = thread::spawn(move || {
println!("thread 1: {}", value);
});
handle1.join().unwrap();
// cannot be sent between threads safely
}
構造体 Rc は参照カウンタの部分に内部可変性を用いており、カウンタが複数のスレッドから変更されてしまうとレースコンディション [7] が発生してしまうため。
レースコンディションを避けるには、ロックやセマフォなどの仕組みを用いて複数のスレッドが同時に共有データにアクセスしないようにする必要がある。
Rust ではそれらの仕組みを内包するスレッドセーフなスマートポインタも提供されており、これらだけに Sync + Send トレイトが実装されている。
Arc<T>
構造体 Arc はスレッドセーフな構造体 Rc であり、Atomically Reference Counted を意味する。[8]
先に整理した Rc<T> を用いた参照カウントのコードは、Arc<T> でも同じように実行できる。
Rc<T> ( 再掲 )
use std::rc::Rc;
fn main() {
let rc1: Rc<String> = Rc::new(String::from("foo"));
println!("refs: {}", Rc::strong_count(&rc1)); // refs: 1
{
let rc2: Rc<String> = rc1.clone();
println!("refs: {}", Rc::strong_count(&rc1)); // refs: 2
println!("refs: {}", Rc::strong_count(&rc2)); // refs: 2
}
println!("refs: {}", Rc::strong_count(&rc1)); // refs: 1
println!("{:?}", rc1.len()); // 3
}
use std::sync::Arc;
fn main() {
let arc1: Arc<String> = Arc::new(String::from("foo"));
println!("refs: {}", Arc::strong_count(&arc1)); // refs: 1
{
let arc2: Arc<String> = arc1.clone();
println!("refs: {}", Arc::strong_count(&arc1)); // refs: 2
println!("refs: {}", Arc::strong_count(&arc2)); // refs: 2
}
println!("refs: {}", Arc::strong_count(&arc1)); // refs: 1
println!("{:?}", arc1.len()); // 3
}
利用側にとっての Rc<T> と Arc<T> の違いは、構造体 Arc は Sync + Send であること。
unsafe impl<T: ?Sized + Sync + Send, A: Allocator + Send> Send for Arc<T, A> {}
unsafe impl<T: ?Sized + Sync + Send, A: Allocator + Sync> Sync for Arc<T, A> {}
したがって、次のように同一の構造体 Arc が所有する値 ( 42 ) に対する複数の参照を複数のスレッドに渡せる。
当然参照カウントもおかしくならない。
use std::sync::Arc;
use std::thread;
fn main() {
let value = Arc::new(42);
let v1 = value.clone();
let handle1 = thread::spawn(move || {
println!("thread 1 value: {}", v1);
let v3 = v1.clone();
println!("thread 1 count: {}", Arc::strong_count(&v3));
});
let v2 = value.clone();
let handle2 = thread::spawn(move || {
println!("thread 2 value: {}", v2);
let v4 = v2.clone();
println!("thread 2 count: {}", Arc::strong_count(&v4));
});
handle1.join().unwrap();
handle2.join().unwrap();
// thread 1 value: 42
// thread 1 count: 3
// thread 2 value: 42
// thread 2 count: 4
}
構造体 Arc は参照カウントのために内部可変性を用いているが、意味的には読み取り用の構造体である。
更新を行いたい場合は別の構造体を用いる。
AtomicT
スレッドセーフなプリミティブ値として構造体 AtomicI32 などが標準ライブラリで提供されている。
実際には次のいずれかの具体的な構造体だが、本記事では便宜上これらを総称して AtomicT と呼ぶ。
AtomicBoolAtomicI8AtomicI16AtomicI32AtomicI64AtomicIsizeAtomicPtrAtomicU8AtomicU16AtomicU32AtomicU64AtomicUsize
AtomicT は名前の通り原子的な操作 [9] ができる値であり、fetch_add メソッドなどを用いてスレッドセーフな更新処理が行える。
use std::sync::atomic::{AtomicI32, Ordering};
fn main() {
let a: AtomicI32 = AtomicI32::new(42);
a.fetch_add(1, Ordering::Relaxed);
println!("{:?}", a); // 43
}
AtomicT も内部可変性を用いており、また Sync + Send である。
メモリオーダリング ( Ordering::Relaxed ) については、本記事では範囲外とする。
AtomicT そのものを複数のスレッドから所有することはできないため、それが可能な構造体 Arc に包んで扱う。
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread;
fn main() {
let value = Arc::new(AtomicUsize::new(0));
let v1 = value.clone();
let handle1 = thread::spawn(move || {
v1.fetch_add(1, Ordering::Relaxed);
});
let v2 = value.clone();
let handle2 = thread::spawn(move || {
v2.fetch_add(1, Ordering::Relaxed);
});
handle1.join().unwrap();
handle2.join().unwrap();
println!("{:?}", *value); // 2
}
AtomicT は、複数のスレッドからカウンタを更新したい場合などに適切である。[10] [11]
Mutex<T>
AtomicT より複雑なデータ構造を複数のスレッドで更新したい場合は、構造体 Mutex を使用する。
ミューテックスは相互排他 ( Mutual Exclusion ) を意味し、ロックを用いて同時アクセスできなくすることでスレッドセーフを実現している。
use std::sync::{Mutex, MutexGuard};
fn main() {
let m = Mutex::new(String::from("foo"));
m.lock().unwrap().push('!');
m.lock().unwrap().push('!');
m.lock().unwrap().push('!');
let mg: MutexGuard<String> = m.lock().unwrap();
println!("{}", mg); // foo!!!
}
lock メソッドで LockResult<MutexGuard<T>> が得られ、ロックできた場合は unwrap メソッドで MutexGuard<T> を取り出す。
構造体 MutexGuard は、内部可変性と DerefMut トレイトにより &mut T のように扱え、Drop トレイトによりロックの解除漏れが発生しないようになっている。
impl<T: ?Sized> Drop for MutexGuard<'_, T> {
fn drop(&mut self) {
unsafe {
...
self.lock.inner.unlock();
}
}
}

ロックが解除されるのは MutexGuard<T> が破棄 ( drop ) されるタイミングなので、次のように変数 mg1 が生きている間はそれより先のコードには到達しない。
use std::sync::Mutex;
fn main() {
let m: Mutex<i32> = Mutex::new(42);
let mg1 = m.lock();
println!("{:?}", mg1); // Ok(42)
let mg2 = m.lock();
println!("{:?}", mg2); // 応答なし
println!("done"); // 到達しない
}
構造体 Mutex は、AtomicT ではない値を複数のスレッドから更新したい場合に使用を検討する。
AtomicT と同様に構造体 Arc に包んで共有する。
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let value = Arc::new(Mutex::new(String::from("foo")));
let v1 = value.clone();
let handle1 = thread::spawn(move || {
v1.lock().unwrap().push('!');
});
let v2 = value.clone();
let handle2 = thread::spawn(move || {
v2.lock().unwrap().push('!');
});
handle1.join().unwrap();
handle2.join().unwrap();
println!("{}", value.lock().unwrap()); // foo!!
}
構造体 Mutex は 1 つの更新か 1 つの参照のどちらかだけを認める厳密なロックを行いたい場合に適切である。
( writer / reader )
RwLock<T>
構造体 Mutex と異なり構造体 RwLock は 1 つの更新か複数の参照が許される。
( writer / readers )
さきほどの構造体 Mutex の例とほぼ同じ次のコードは、複数の read が許されるためロック待ちが発生しない。
use std::sync::RwLock;
fn main() {
let rw: RwLock<i32> = RwLock::new(42);
let r1 = rw.read();
println!("{:?}", r1); // Ok(42)
let r2 = rw.read();
println!("{:?}", r2); // Ok(42)
println!("done"); // done
}
reader が存在する場合、writer は生まれない。
use std::sync::RwLock;
fn main() {
let rw: RwLock<i32> = RwLock::new(42);
let r1 = rw.read();
println!("{:?}", r1); // Ok(42)
let w1 = rw.write();
println!("{:?}", w1); // 応答なし
println!("done"); // 到達しない
}
writer が存在する場合、reader 生成は失敗する。
use std::sync::RwLock;
fn main() {
let rw: RwLock<i32> = RwLock::new(42);
let w1 = rw.write();
println!("{:?}", w1); // Ok(42)
let r1 = rw.read();
println!("{:?}", r1); // rwlock read lock would result in deadlock
println!("done"); // 到達しない
}
内部可変性や Deref トレイトや Drop トレイトについては、構造体 Mutex とほぼ同じ。

頻繁に参照されるが更新頻度は低い値 ( e.g. プロダクトの実行時設定 ) を扱う場合は、Mutex<T> より RwLock<T> が適切な場合がある。
まとめ
| 概要 | |
|---|---|
Box<T> |
サイズのわからない値をスタックで所有できる |
Rc<T> |
同じ値を複数の値から所有できる |
Cell<T> |
不変参照でも更新できる ( Copy できる値のみ ) |
RefCell<T> |
不変参照でも更新できる |
Arc<T> |
スレッドセーフな Rc<T>
|
AtomicT |
スレッドセーフに更新できるプリミティブ値 |
Mutex<T> |
スレッドセーフに更新できる任意の値 ( write / reader ) |
RwLock<T> |
スレッドセーフに更新できる任意の値 ( write / readers ) |
Memory Container Cheat-sheet
ここまで理解すればチートシートを理解できる。

以下、ざっくりペン入れ。
シングルスレッドでいい場合。
余計なコストがかからない。

複数の値で所有したいなら、構造体 Rc が確定。

サイズ不定のものをスタックで持ちたいなら、ヒープに展開する構造体 Box で確定。

mut なしに変更したいなら、構造体 Cell か構造体 RefCell が確定。
使い分けは Copy トレイトを実装してるか次第。

マルチスレッドの場合。
構造体と所有する T に Sync + Send が求められる。

複数の値で所有したいなら、構造体 Arc が確定。
構造体 Rc と同様。

書き込みが少なく読み込みの複数同時アクセスを許容できるなら、構造体 RwLock で確定。

読み込みの同時アクセスを認めないなら、AtomicT か構造体 Mutex で確定。
使い分けはプリミティブであり標準ライブラリに AtomicT が用意されているかどうか。

ざっくりだが十分だろう。
おわりに
ぶっちゃけ、まじめに調べている間に理解してしまいチートシートはいらなくなった。
ずっといつか理解したいと思っていたので、ちゃんと学べてよかった。
-
プロセス起動時に割り当てられる領域で、ヒープと比較して高速にアクセスできるが小さい。 ↩︎
-
必要に応じてプロセスが実行時に割り当てる領域で、スタックと比較してアクセスが遅いが大きい。 ↩︎
-
スマートポインタには追加のメタデータなどがあるが、それらのサイズは固定なので構造体自体のサイズも固定。 ↩︎
-
構造体
Cellの実装内部ではunsafeが使われている。 ↩︎ -
構造体
Rcの参照カウントが増減するのも内部可変性によるもの。 ↩︎ -
通知を行う構造体は
mutを持たないが、同一のトレイトを実装するモックは IO ではなく通知内容をベクターにためたいというもの。一部のトレイト実装だけmutを付けることはできないが、内部可変性を用いるとmutをつけないままベクターを更新できる。 ↩︎ -
共有データへの更新が複数のスレッドから発生し、変更結果が意図しない状態になること。たとえばスレッド A と B で「A が 0 をよみとり」「B が 0 をよみとり」「A が +1」「B が +1」すると最終結果が 1 になってしまう。 ↩︎
-
Atomically ( 原子的 ): 分割できないというニュアンスで理解するとわかりいい。参照処理と更新処理が不可分である。更新処理は行われているか完全に終わっているかしかないため、割り込みが起きずレースコンディションが発生しない。 ↩︎
-
参照と更新が不可分である。 ↩︎
-
詳細は次の課題とするが、おそらく
AtomicTはセマフォで実現されている。アトミック操作はプラットフォームの低レベルな機能に依存しているため、Atomic<T>のようにいずれの型にも対応させることができない。 ↩︎
Discussion