changed 4 years ago
Linked with GitHub

Rust勉強会 第16回

15章 後半

2020/11/20 岡本拓海


本日のメニュー

  • REfCell<T>と内部可変性パターン
  • 循環参照について

プレゼン20分くらい+議論10分位で18:30前後終了を目指します。


RefCell<T>で実行時に借用規則を強制する

第4章で学んだ借用規則:

  • いかなる時も(以下の両方ではなく、)1つの可変参照かいくつもの不変参照のどちらかが可能になる
  • 参照は常に有効でなければならない。

RefCell<T>で実行時に借用規則を強制する

  • Rc<T>は、同じデータに複数の所有者を持たせてくれる; Box<T>とRefCell<T>は単独の所有者。
  • Box<T>では、不変借用も可変借用もコンパイル時に精査できる; Rc<T>では不変借用のみがコンパイル時に精査できる; RefCell<T>では、不変借用も可変借用も実行時に精査される。
  • RefCell<T>は実行時に精査される可変借用を許可するので、RefCell<T>が不変でも、 RefCell<T>内の値を可変化できる。

内部可変性:不変値への可変借用

これは動かない

fn main() { let x = 5; let y = &mut x; }

内部可変性のユースケース: モックオブジェクト

pub trait Messenger { fn send(&self, msg: &str); } pub struct LimitTracker<'a, T: 'a + Messenger> { messenger: &'a T, value: usize, max: usize, } impl<'a, T> LimitTracker<'a, T> where T: Messenger { pub fn new(messenger: &T, max: usize) -> LimitTracker<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 >= 0.75 && percentage_of_max < 0.9 { // 警告: 割り当ての75%以上を使用してしまいました self.messenger.send("Warning: You've used up over 75% of your quota!"); } else if percentage_of_max >= 0.9 && percentage_of_max < 1.0 { // 切迫した警告: 割り当ての90%以上を使用してしまいました self.messenger.send("Urgent warning: You've used up over 90% of your quota!"); } else if percentage_of_max >= 1.0 { // エラー: 割り当てを超えています self.messenger.send("Error: You are over your quota!"); } } }

循環参照


循環参照させる

use std::rc::Rc; use std::cell::RefCell; use List::{Cons, Nil}; #[derive(Debug)] enum List { Cons(i32, RefCell<Rc<List>>), Nil, } impl List { fn tail(&self) -> Option<&RefCell<Rc<List>>> { match *self { Cons(_, ref item) => Some(item), Nil => None, } } }

循環参照させる

fn main() { let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil)))); // aの最初の参照カウント = {} println!("a initial rc count = {}", Rc::strong_count(&a)); // aの次の要素は = {:?} println!("a next item = {:?}", a.tail()); let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a)))); // b作成後のaの参照カウント = {} println!("a rc count after b creation = {}", Rc::strong_count(&a)); // bの最初の参照カウント = {} println!("b initial rc count = {}", Rc::strong_count(&b)); // bの次の要素 = {:?} println!("b next item = {:?}", b.tail()); 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)); // aを変更後のaの参照カウント = {} println!("a rc count after changing a = {}", Rc::strong_count(&a)); // Uncomment the next line to see that we have a cycle; // it will overflow the stack // 次の行のコメントを外して循環していると確認してください; スタックオーバーフローします // println!("a next item = {:?}", a.tail()); // aの次の要素 = {:?} }

循環参照させる


循環参照を回避する: Rc<T>をWeak<T>に変換する




子供から親に参照を追加する


木データ構造を作る: 子ノードのあるNode

use std::rc::Rc; use std::cell::RefCell; #[derive(Debug)] struct Node { value: i32, children: RefCell<Vec<Rc<Node>>>, }

strong_countとweak_countへの変更を可視化する

fn main() { let leaf = Rc::new(Node { value: 3, parent: RefCell::new(Weak::new()), children: RefCell::new(vec![]), }); println!( // leafのstrong_count = {}, weak_count = {} "leaf strong = {}, weak = {}", Rc::strong_count(&leaf), Rc::weak_count(&leaf), ); { let branch = Rc::new(Node { value: 5, parent: RefCell::new(Weak::new()), children: RefCell::new(vec![Rc::clone(&leaf)]), }); *leaf.parent.borrow_mut() = Rc::downgrade(&branch); println!( // branchのstrong_count = {}, weak_count = {} "branch strong = {}, weak = {}", Rc::strong_count(&branch), Rc::weak_count(&branch), ); println!( "leaf strong = {}, weak = {}", Rc::strong_count(&leaf), Rc::weak_count(&leaf), ); } println!("leaf parent = {:?}", leaf.parent.borrow().upgrade()); println!( "leaf strong = {}, weak = {}", Rc::strong_count(&leaf), Rc::weak_count(&leaf), ); }









参考資料

https://doc.rust-jp.rs/book-ja/ch12-00-an-io-project.html


ご清聴ありがとうございました

Select a repo