目次
- 15.0 概要
-
15.1
Box<T>
: ヒープのデータを指す -
15.2
Deref
トレイトでスマートポインタを普通の参照のように扱う -
15.3
Drop
トレイトで片付け時にコードを走らせる -
15.4
Rc<T>
は、参照カウント方式のスマートポインタ -
15.5
RefCell<T>
と内部可変性パターン - 15.6 循環参照は、メモリをリークすることもある
15.0 概要
- スマートポインタは、ポインタのように振る舞うだけでなく、追加のメタデータと能力があるデータ構造
- スマートポインタは、参照とは異なり、指しているデータを所有する
- 普通、構造体を使用して実装されている
- スマートポインタは通常の構造体と異なり、
Deref
とDrop
トレイトを実装している-
Deref
:参照外し演算子*
を作用させたときの動作を定義する -
Drop
:値がスコープを抜けるときの動作を定義する
-
- 既出の例:
String
,Vec<T>
- この章で取り扱う新しい例:
-
Box<T>
: ヒープに値を確保する -
Rc<T>
: 複数の所有権を可能にする参照カウント型 -
Ref<T>
,RefMut<T>
:RefCell<T>
を通してアクセスされ、コンパイル時ではなく実行時に借用規則を強制する型-
内部可変性パターン: 不変な型が、内部の値を変更するための API を公開する(
RefCell<T>
の使用例) -
循環参照:Rust でもメモリリークの可能性がある(
Rc<T>
とRefCell<T>
を組合わせると循環参照が発生しうる)
-
内部可変性パターン: 不変な型が、内部の値を変更するための API を公開する(
-
- その他、この章では扱わない例として、
Cell<T>
やMutex<T>
が挙げられる(標準ライブラリのドキュメンテー
ションをチェックせよ)
各参照を選択する理由
-
Rc<T>
は、同じデータに複数の所有者を持たせてくれる- 一方、
Box<T>
とRefCell<T>
は単独の所有者
- 一方、
-
Box<T>
では、不変借用も可変借用もコンパイル時に精査できる-
Rc<T>
では不変借用のみがコンパイル時に精査できる -
RefCell<T>
では、不変借用も可変借用も実行時に精査される
-
-
RefCell<T>
は実行時に精査される可変借用を許可する(不変借用も作成可能)-
RefCell<T>
が不変でも、RefCell<T>
内の値を可変化できる
-
その他の重要な概念
- 参照外し型強制
15.1 Box<T>
: ヒープのデータを指す
-
Box<T>
により、スタックではなくヒープにデータを格納できる- スタックにはヒープデータへのポインタが作成される
- 使用する場面:
- コンパイル時にはサイズを知ることができない型があり、正確なサイズを要求する文脈でその型の値を使用する時
- 多くのデータがあり、その所有権を移したいが、その際にデータがコピーされないようにしたい時(パフォーマンスが向上する)
- 値を所有する必要があり、特定の型であることではなく、特定のトレイトを実装する型であることのみ気にかけている時(トレイトオブジェクト)
Box<T>
を使ってヒープにデータを格納する
-
Box::new
メソッドでボックスを生成できる - ボックスは、スコープを抜けるとき、メモリから解放される
-
メモリの解放は、以下の二つに対して起きる:
- (スタックに格納されている)ボックス
- (ヒープに格納されている)データ
-
例:ボックスを使用してヒープに
i32
の値を格納する- 値
5
はヒープに確保される -
main
関数の終わりでb
は解放される
fn main() { let b = Box::new(5); println!("b = {}", b); // b = 5 }
- 値
-
ボックスで再帰的な型を可能にする
- 再帰的な型 はコンパイル時にサイズがわからないので、ボックスを利用して実装する必要がある
再帰的なデータ型の例:コンスリスト
コンスリストとは
- コンスリストの各要素は、二つの要素を含む:
- 現在の要素の値
- 次の要素
- リストの最後の要素は
Nil
と呼ばれる値だけを含む(null
,nil
とは異なるので注意)
問題のある定義
-
コンスリストの
enum
定義(この時点では List 型のサイズが不明なのでコンパイル不可):enum List { Cons(i32, List), Nil }
-
使用例:
use List::{Cons, Nil}; fn main() { let list = Cons(1, Cons(2, Cons(3, Nil))); }
-
Box<T>
を用いて既知のサイズの再帰的な型を得る
-
間接参照(値の代わりに値へのポインタを格納する)を利用して再帰的な型を実現する
-
使用例:
-
List
列挙子は1つのi32
のサイズに加えてボックスのポインタデータを格納する領域を必要とする(がそれ以上は必要としない)- --> コンパイラは
List
値を格納するのに必要なサイズを計算できる
- --> コンパイラは
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) )) )) ); }
-
15.2 Deref
トレイトでスマートポインタを普通の参照のように扱う
-
Deref
トレイトを実装することで、参照外し演算子の*
の振る舞いをカスタマイズできる - 参照外し型強制で参照やスマートポインタをうまく使うことができる
通常の参照に参照外し演算子を適用して値までポインタを追いかける
-
例:i32 値への参照を生成してから参照外し演算子を使ってデータまで参照を辿る
fn main() { let x = 5; let y = &x; assert_eq!(x, 5); assert_eq!(*y, 5): }
Box<T>
に参照外し演算子を適用して値までポインタを追いかける
-
例:i32 値への参照を生成してから参照外し演算子を使ってデータまで参照を辿る
fn main() { let x = 5; let y = Box::new(x); assert_eq!(x, 5); assert_eq!(*y, 5): }
独自のスマートポインタを定義する(Box<T>
に似たスマートポインタの定義)
-
Box<T>
型を模倣するために一要素のタプル構造を定義する:struct MyBox<T> (T); impl<T> MyBox<T> { fn new(x: T) -> MyBox<T> { MyBox<T>(x) } }
-
MyBox<T>
の参照外しの方法を指定していないので、以下のコードはコンパイルできない:fn main() { let x = 5; let y = MyBox::new(x); assert_eq!(x, 5); assert_eq!(*y, 5); }
Deref
トレイトを実装して型を参照のように扱う
-
*
演算子で参照外しできるようにするには、Deref
トレイトを実装する必要がある-
Deref
トレイトはderef
という 1 つのメソッドの実装を要求する-
deref
はself
を借用し、内部のデータへの参照を返すメソッド
-
- また、
type Target = Hoge;
でDeref
トレイトが使用する関連型を定義する(参照外しの結果得られる値の型を定義する?) - 例:
use std::ops::Deref; struct MyBox<T> (T); impl<T> Deref for MyBox<T> { type target = T; fn deref(&self) -> &T { &self.0 } }
-
-
Deref
トレイトを実装している型の変数に参照外しを行うと Rust は水面下で+(hoge.deref())
のようなコードを実行する- (Rust が
*
演算子をderef
メソッドの呼び出しと普通の参照外しへと置き換えてくれる) - (普通の参照か
Deref
を実装した型であるかどうかに関わらず、等しく機能するコードを書ける)
- (Rust が
-
*
演算子がderef
メソッドの呼び出しと*
演算子の呼び出しに置き換えられるのは、コード内で*
を打つ毎にただ 1 回だけ- (
*
演算子の置き換えは無限に繰り返されない)
- (
関数やメソッドで暗黙的な参照外し型強制
-
参照外し型強制:
Deref
を実装する型への参照が自動で型変換される -
例:
Deref
トレイトを実装しているMyBox
型のインスタンスへの参照を&str
型の引数を期待する関数に渡すと型強制が行われる:- ここでは、
MyBox<T>
型に実装されたderef
メソッドによって&MyBox<String>
が&String
に変換され、String
型に実装されたderef
メソッドによって、&String
型が&str
に変換される
// hello 関数は &str 型の引数を期待する fn hello(name: &str) { println!("Hello, {}!", name); } fn main() { let m = MyBox::new(String::from("Rust")); hello(&m); // &str を期待する関数に &MyBox<String> を渡すと参照外し型強制される // このような参照外し型強制が Rust の機能として実装されていなければ、以下のようなコードを書く必要がある(この機能によりコードがかなり簡素化されている): // hello(&(*m)[..]); }
- ここでは、
参照外し型強制を可変性と組合わせる
-
DerefMut
トレイトを実装することで、可変参照に*
演算子を作用させたときに可変参照を得る動作を定義できる- (
Deref
トレイトを実装することで、不変参照に*
演算子を作用させたときに参照を定義できることに対応)
- (
-
コンパイラは以下の三つの場合に参照外し型強制を実行する:
-
T: Deref<Target=U>
の時、&T
から&U
-
T: DerefMut<Target=U>
の時、&mut T
から&mut U
-
T: Deref<Target=U>
の時、&mut T
から&U
-
&T
があり、T
が何らかの型U
へのDeref
を実装しているなら、透過的に&U
を得られる -
&mut T
があり、T
が何らかの型U
へのDerefMut
を実装しているなら、透過的に&mut U
を得られる -
&mut T
があり、T
が何らかの型U
へのDeref
を実装しているなら、透過的に&U
を得られる
※不変参照を参照外し型強制で可変参照に変えることはできない
-
15.3 Drop
トレイトで片付け時にコードを走らせる
-
どんな型に対しても
Drop
トレイトの実装を提供できる- スコープを抜けそうになった時に起こることをカスタマイズできる
-
Drop
トレイトを実装するには、self
への可変参照を取るdrop
という 1 つのメソッドを実装する必要がある-
drop
関数には、自分の型のインスタンスがスコープを抜ける時に走らせたいロジックを実装する
-
-
例:
struct CustomSmartPointer { data: String, } impl Drop for CustomSmartPointer { fn drop(&mut self) { println!("Dropping CustomSmartPointer with data `{}`!", self.data); } } fn main() { let c = CustomSmartPointer { data: String::from("my stuff"), }; let d = CustomSmartPointer { data: String::from("other stuff"), }; println!("CustomSmartPointers created."); } // ここで "Dropping CustomSmartPointer with data `other stuff`!" と "Dropping CustomSmartPointer with data `my stuff`!" が標準出力に表示される // 変数は、生成されたのと逆の順序でドロップされるので、`d` は `c` より先にドロップされる
std::mem::drop
で早期に値をドロップする
-
Rust は、
Drop
トレイトのdrop
メソッドを手動で呼ばせてくれない -
スコープが終わる前に値を強制的にドロップさせたいなら、代わりに標準ライブラリが提供する
std::mem::drop
関数を呼ぶ- (この関数は初期化処理に含まれている)
-
例:
fn main() { let c = CustomSmartPointer { data: String::from("some data") }; println!("CustomSmartPointer created."); drop(c); println!("CustomSmartPointer dropped before the end of main."); }
-
このコードを実行すると以下のように出力される(
Drop
トレイトで定義したdrop
メソッドがdrop(c);
の部分で実行される):CustomSmartPointer created. Dropping CustomSmartPointer with data `some data`! CustomSmartPointer dropped before the end of main.
-
15.4 Rc<T>
は、参照カウント方式のスマートポインタ
-
単独の値が複数の所有者を持つ場合もある
- 例:グラフデータ構造では、複数の辺が同じノードを指す可能性がある。概念的にそのノードはそれを指す全ての辺に所有されるので、指す辺がなくならない限り、ノードは片付けられるべきではない。
-
Rc<T>
という型(reference counting(参照カウント)の略)によって、複数の所有権が可能になる -
Rc<T>
型は、値への参照の数を追跡する- これにより値がまだ使用中かどうか決定される
- 値への参照が 0 なら、どの参照も無効にすることなく、値は片付けられる
-
ヒープにプログラムの複数箇所で読む何らかのデータを確保したいが、コンパイル時にはどの部分が最後にデータを使用し終わるか決定できない時に
Rc<T>
型を使用する -
※
Rc<T>
は、シングルスレッドの筋書きで使用するためだけのもの
Rc<T>
の使い方の例
-
Rc<T>
の使い方:-
use
文を追加してRc<T>
をスコープに導入 -
let hoge = Rc::new(fuga)
でfuga
への参照カウンタを作成し、 -
Rc::clone(&hoge)
でhoge
のクローンを作成すると参照カウンタのカウンタ値が+1される
-
-
例:
5
と10
を含むコンスリストa
を作る。さらに、3
で始まるb
と4
で始まるc
のふたつのコンスリストを作る。b
とc
のどちらもa
リストに接続する- (
b
,c
のコンスリストはどちらも同じコンスリストa
を共有する)
- (
-
以下のような単純な実装はコンパイルエラーを吐く:
enum List { Cons(i32, Box<List>), Nil, } use crate::List::{Cons, Nil}; fn main() { let a = Cons(5, Box::new(Cons(10, Box::new(Nil)))); let b = Cons(3, Box::new(a)); // コンスリスト a のヒープデータの所有権は,この行で b にムーブ済みなので let c = Cons(4, Box::new(a)); // ここではもう a を呼び出せない }
-
そこで、
Box<T>
の代わりにRc<T>
を使用することでコンスリストの各要素が複数の所有者を持てるように変更する:- 新しい所有者を生み出す際には、
Rc::clone
を実行して値をクローンする(これを実行する度に、参照カウンタの値が1増える) - 初期化処理に含まれていないので、use 文を追加して
Rc<T>
をスコープに導入する
enum List { Cons(i32, Rc<List>), Nil, } use crate::List::{Cons, Nil}; use std::rc::Rc; fn main() { let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil))))); let b = Cons(3, Rc::clone(&a)); let c = Cons(4, Rc::clone(&a)); }
- 新しい所有者を生み出す際には、
参照カウントが変化することを確認する(Rc::strong::count
関数)
-
Rc::strong::count
関数で参照カウントのカウント値を取得できる -
例:
enum List { Cons(i32, Rc<List>), Nil, } use crate::List::{Cons, Nil}; use std::rc::Rc; fn main() { let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil))))); println!("count after creating a = {}", Rc::strong_count(&a)); // count after creating a = 1 let b = Cons(3, Rc::clone(&a)); println!("count after creating b = {}", Rc::strong_count(&a)); // count after creating b = 2 { let c = Cons(4, Rc::clone(&a)); println!("count after creating c = {}", Rc::strong_count(&a)); // count after creating c = 3 } println!("count after c goes out of scope = {}", Rc::strong_count(&a)); // count after c goes out of scope = 2 }
15.5 RefCell<T>
と内部可変性パターン
-
内部可変性:そのデータへの不変参照がある時でさえもデータを可変化できるデザインパターン
- (
unsafe
を用いて実装されている)
- (
RefCell<T>
で実行時に借用規則を強制する
-
RefCell<T>
型は、保持するデータに対して単独の所有権を表す - 以下の借用規則 a, b の取り扱いが通常の参照と異なる:
- 通常の参照
&hoge
とBox<T>
は以下のようなルールがコンパイル時に強制される(破るとコンパイルエラーになる)- 参照は、一つの可変参照か複数の不変参照
- 参照は常に有効でなければならない
- 一方、
RefCell<T>
では上記の借用規則は実行時に強制される(破ると実行時にパニックを起こす)
- 通常の参照
-
RefCell<T>
インスタンスに対して以下のメソッドを呼び出すことでスマートポインタを生成できる:-
borrow
メソッド:Ref<T>
を返す(≈ 不変参照) -
borrow_mut
メソッド:RefMut<T>
を返す(≈ 可変参照)
-
- cf. 通常の参照:
&hoge
:不変参照&mut hoge
:可変参照
-
どちらの参照も
Deref
トレイトを実装しているので、普通の参照のように扱える -
RefCell<T>
はRef<T>
型あるいはRefMut<T>
型のスマートポインタの数を数える:-
borrow
メソッド(borrow_mut
メソッド)を呼び出すたびに不変参照(可変参照)のカウント値を増やす -
Ref<T>
(RefMut<T>
)の値がスコープを抜けるたびに不変参照(可変参照)のカウント値を減らす - 実行時に、借用規則(参照は、一つの可変参照か複数の不変参照)が破られるとパニックを起こす
-
例:可変参照を複数生成するとパニックが起こる(コンパイルできる)
impl Messenger for MockMessenger { fn send(&self, message: &str) { let mut one_borrow = self.sent_messages.borrow_mut(); let mut two_borrow = self.sent_messages.borrow_mut(); one_borrow.push(String::from(message)); two_borrow.push(String::from(message)); } }
-
-
-
コードが借用規則に従っているとプログラマは確証を得ているが、コンパイラがそれを理解し保証することができない時に
RefCell<T>
型は有用 -
※
RefCell<T>
もシングルスレッドの筋書きで使用するためのもの(マルチスレッドの文脈で使ってみようとすると、コンパイルエラーを出す)
内部可変性のユースケース:モックオブジェクト(RefCell<T>
の使用例)
-
テストダブル:ソフトウェアテストにおいて、テスト対象が依存しているコンポーネントを置き換える代用品のこと
-
モックオブジェクト:テスト中に起きることを記録するテストダブルの特定の型
-
考えるテストの筋書き:
-
値を最大値に対して追跡し、現在値がどれくらい最大値に近いかに基づいてメッセージを送信するライブラリを作成
-
ライブラリのコードは以下のような感じ:
pub trait Messenger { fn send(&self, msg: &str); } pub struct LimitTracker<'a, T: Messenger> { messenger: &'a T, value: usize, max: usize, } impl<'a, T> LimitTracker<'a, T> where T: Messenger, { pub fn new(messenger: &'a T, max: usize) -> LimitTracker<'a, T> { LimitTracker { messenger, value: 0, max, } } pub fn set_value(&mut self, value: usize) { self.value = value; let percentage_of_max = self.value as f64 / self.max as f64; if percentage_of_max >= 1.0 { self.messenger.send("Error: You are over your quota!"); } else if percentage_of_max >= 0.9 { self.messenger .send("Urgent warning: You've used up over 90% of your quota!"); } else if percentage_of_max >= 0.75 { self.messenger .send("Warning: You've used up over 75% of your quota!"); } } }
-
-
重要な点:
-
Messenger
トレイトには、self
への不変参照とメッセージのテキストを取るsend
というメソッドが 1 つある -
LimitTracker
のset_value
メソッドの振る舞いをテストしたい- しかし、
set_value
メソッドはassert
できるものを返してくれない...
- しかし、
-
-
-->
LimitTracker
のset_value
メソッドをテストするために、Messenger
トレイトを実装するモックオブジェクトを作成して、そのモックオブジェクトが期待通りのメッセージをもつことを確認することでテストを実行したい... -
--> 以下のような実装を考える(しかし、これは借用チェッカーに拒否される):
- ここで、もともと、
LimitTracker
のmessenger
が不変借用される想定であり、send
メソッドがself
への不変参照をとることを考えると、mock_messenger
が内部にVec<String>
を持ち、そこにメッセージを蓄えるという設計であることは借用規則に反している:
#[cfg(test)] mod tests { use super::*; struct MockMessenger { sent_messages: Vec<String>, } impl MockMessenger { fn new() -> MockMessenger { MockMessenger { sent_messages: vec![], } } } impl Messenger for MockMessenger { fn send(&self, message: &str) { self.sent_messages.push(String::from(message)); } } #[test] fn it_sends_an_over_75_percent_warning_message() { let mock_messenger = MockMessenger::new(); let mut limit_tracker = LimitTracker::new(&mock_messenger, 100); limit_tracker.set_value(80); assert_eq!(mock_messenger.sent_messages.len(), 1); } }
- ここで、もともと、
-
--> この問題は内部可変性を使えば解消できる:
#[cfg(test)] mod tests { use super::*; + use std::cell::RefCell; struct MockMessenger { - sent_messages: Vec<String>, + sent_messages: RefCell<Vec<String>>, } impl MockMessenger { fn new() -> MockMessenger { MockMessenger { - sent_messages: vec![], + sent_messages: RefCell::new(vec![]), } } } impl Messenger for MockMessenger { fn send(&self, message: &str) { - self.sent_messages.push(String::from(message)); + self.sent_messages.borrow_mut().push(String::from(message)); // borrow_mut() で可変参照を一時的に得て値を push する } } #[test] fn it_sends_an_over_75_percent_warning_message() { // --snip-- let mock_messenger = MockMessenger::new(); let mut limit_tracker = LimitTracker::new(&mock_messenger, 100); limit_tracker.set_value(80); - assert_eq!(mock_messenger.sent_messages.len(), 1); + assert_eq!(mock_messenger.sent_messages.borrow().len(), 1); // borrow() で不変参照を得る } }
Rc<T>
と RefCell<T>
を組み合わせることで可変なデータに複数の所有者を持たせる
-
Rc<T>
:何らかのデータに複数の所有者を作成できるが、不変のアクセスしかさせてくれない -
RefCell<T>
を抱えるRc<T>
(Rc<RefCell<T>>
)を作成することで、複数の所有者を持ちつつ、可変的な値を扱える -
例:複数の所有者に所有される要素からなるコンスリストを可変化する
#[derive(Debug)] enum List { Cons(Rc<RefCell<i32>>, Rc<List>), Nil, } use crate::List::{Cons, Nil}; use std::cell::RefCell; use std::rc::Rc; fn main() { let value = Rc::new(RefCell::new(5)); // Rc で包むことで `RefCell { value: 5 }` の所有権を複数生成できるようにする. RefCell で包むことで `5` の不変・可変参照に関する借用規則は実行時にチェックされる let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil))); // a にも 'value の Rc の中身'の所有権を共有 let b = Cons(Rc::new(RefCell::new(3)), Rc::clone(&a)); // b にも 'a の Rc の中身'の所有権を共有 let c = Cons(Rc::new(RefCell::new(4)), Rc::clone(&a)); // c にも 'a の Rc の中身'の所有権を共有 *value.borrow_mut() += 10; // value の borrow_mut メソッドを呼び出すことで自動参照外しして Rc を剥きながら、RefMut (RefCell の中身への可変参照)に変換し、この可変参照に対して `*` を適用することで value の中身 `5` を可変的に扱っている println!("a after = {:?}", a); // a after = Cons(RefCell { value: 15 }, Nil) println!("b after = {:?}", b); // b after = Cons(RefCell { value: 3 }, Cons(RefCell { value: 15 }, Nil)) println!("c after = {:?}", c); // c after = Cons(RefCell { value: 4 }, Cons(RefCell { value: 15 }, Nil)) }
15.6 循環参照は、メモリをリークすることもある
-
Rc<T>
とRefCell<T>
を組合わせると循環参照によって参照のカウント値が0にならず、メモリリークを起こす例を構築できる - すなわち、Rust ではメモリリークを完全に回避することはできない
-
Rc<T>
値を含むRefCell<T>
値があるなどの内部可変性と参照カウントのある型がネストして組み合わさっていたら、循環していないことをプログラマの側で保証しなければならない!(コンパイラは保証してくれない...)
循環参照の例
-
例:循環参照が起こる例
use crate::List::{Cons, Nil}; use std::cell::RefCell; use std::rc::Rc; #[derive(Debug)] enum List { Cons(i32, RefCell<Rc<List>>), Nil, } impl List { fn tail(&self) -> Option<&RefCell<Rc<List>>> { match self { Cons(_, item) => Some(item), Nil => None, } } } fn main() { let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil)))); println!("a initial rc count = {}", Rc::strong_count(&a)); // 1 println!("a next item = {:?}", a.tail()); // Some(RefCell { value: Nil }) let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a)))); println!("a rc count after b creation = {}", Rc::strong_count(&a)); // 2 println!("b initial rc count = {}", Rc::strong_count(&b)); // 1 println!("b next item = {:?}", b.tail()); // Some(RefCell { value: Cons(5, RefCell { value: Nil }) }) if let Some(link) = a.tail() { *link.borrow_mut() = Rc::clone(&b); // ここで a のしっぽを b に向けさせて循環を作成する } println!("b rc count after changing a = {}", Rc::strong_count(&b)); // 2 println!("a rc count after changing a = {}", Rc::strong_count(&a)); // 2 // Uncomment the next line to see that we have a cycle; // it will overflow the stack // println!("a next item = {:?}", a.tail()); }
循環参照を回避する: Rc<T>
を Weak<T>
に変換する
-
Rc::clone
を呼び出して、Rc<T>
インスタンスのstrong_count
を +1 する代わりに、 -
Rc:downgrade
を呼び出して、Rc<T>
インスタンスのweak_count
を +1することができる(Weak<T>
型のスマートポインタが得られる)-
strong_count
が 0 になると、Rc<T>
インスタンスが片づけられるが、weak_count
が 0 になってもとくに何も起こらない
-
-
Weak<T>
が参照する値はドロップされてしまっている可能性がある- →
Weak<T>
のupgrade
メソッドを呼び出すことでOption<Rc<T>>
を取得する
- →
要するに
-
Rc::downgrade
:Rc::clone
の代わり.-
weak_count
を +1 する. -
Weak<T>
型のスマートポインタを生成
-
-
Weak<T>
のupgrade
メソッド:参照外し兼Option
の unwrap
Weak<T>
の使用例:木データ構造を作る(子ノードのある Node)
-
Node
構造体を定義:-
value
:各ノードの値 -
children
:ノードに続くノードの配列
-
use std::cell::RefCell;
use std::rc::Rc;
#[derive(Debug)]
struct Node {
value: i32,
children: RefCell<Vec<Rc<Node>>>,
}
fn main() {
let leaf = Rc::new(Node {
value: 3,
children: RefCell::new(vec![]),
});
let branch = Rc::new(Node {
value: 5,
children: RefCell::new(vec![Rc::clone(&leaf)]),
});
}
子供から親への参照を追加する
-
Node
構造体にparent
フィールドを追加する- 循環参照を避けるために
parent
の型はRefCell<Weak<Node>>
とする(RefCell
はparent
を可変化するために使用) - ただし、親ノードは子ノードを所有すべき(親が消えたら子も消えるべき)なので、
children
の型はRefCell<Vec<Rc<Node>>>
のままでよい
- 循環参照を避けるために
use std::cell::RefCell;
use std::rc::{Rc, Weak};
#[derive(Debug)]
struct Node {
value: i32,
parent: RefCell<Weak<Node>>,
children: RefCell<Vec<Rc<Node>>>,
}
fn main() {
let leaf = Rc::new(Node {
value: 3,
parent: RefCell::new(Weak::new()), // `Weak::new` で中身が空の `Weak<T>` スマートポインタを作成
children: RefCell::new(vec![]),
});
println!("leaf parent = {:?}", leaf.parent.borrow().upgrade()); // leaf parent = None (この時点では `leaf.parent` の中身は `None`)
let branch = Rc::new(Node {
value: 5,
parent: RefCell::new(Weak::new()), // `Weak::new` で中身が空の `Weak<T>` スマートポインタを作成
children: RefCell::new(vec![Rc::clone(&leaf)]),
});
// `parent` の `RefCell` の中身(`Weak<Node>` 型)に対して参照外し演算子 `*` を作用させて `parent` の中身に変更を加えている.
// `Rc::downgrade` で参照 `&branch` を `Weak<T>` 型のスマートポインタに変換している
*leaf.parent.borrow_mut() = Rc::downgrade(&branch);
println!("leaf parent = {:?}", leaf.parent.borrow().upgrade()); /*
leaf parent = Some(Node { value: 5, parent: RefCell { value: (Weak) },
children: RefCell { value: [Node { value: 3, parent: RefCell { value: (Weak) },
children: RefCell { value: [] } }] } })
(この時点では `leaf.parent` の中に `Weak<Rc<Node>>` 型の値(`branch`)が入っている)
*/
}
strong_count
と weak_count
への変更を可視化する
-
strong_count
とweak_count
への変更を観察する
use std::cell::RefCell;
use std::rc::{Rc, Weak};
#[derive(Debug)]
struct Node {
value: i32,
parent: RefCell<Weak<Node>>,
children: RefCell<Vec<Rc<Node>>>,
}
fn main() {
let leaf = Rc::new(Node {
value: 3,
parent: RefCell::new(Weak::new()),
children: RefCell::new(vec![]),
});
println!(
"leaf strong = {}, weak = {}",
Rc::strong_count(&leaf),
Rc::weak_count(&leaf),
); // leaf strong = 1, weak = 0
{
let branch = Rc::new(Node {
value: 5,
parent: RefCell::new(Weak::new()),
children: RefCell::new(vec![Rc::clone(&leaf)]), // leaf の strong_count +1
});
*leaf.parent.borrow_mut() = Rc::downgrade(&branch); // branch の weak_count +1
println!(
"branch strong = {}, weak = {}",
Rc::strong_count(&branch),
Rc::weak_count(&branch),
); // branch strong = 1, weak = 1
println!(
"leaf strong = {}, weak = {}",
Rc::strong_count(&leaf),
Rc::weak_count(&leaf),
); // leaf strong = 2, weak = 0
} // ここで `branch` がドロップする(leaf の strong_count が -1)
println!("leaf parent = {:?}", leaf.parent.borrow().upgrade()); // leaf parent = None
println!(
"leaf strong = {}, weak = {}",
Rc::strong_count(&leaf),
Rc::weak_count(&leaf),
); // leaf strong = 1, weak = 0
}