借用規則をコンパイル時でなく実行時に適用する。
成果物
参考
- https://doc.rust-jp.rs/book/second-edition/ch15-05-interior-mutability.html
- 停止性問題
- std::cell::RefCell
RefCell
RefCell
はスマートポインタの一種。
内部可変性
内部可変性は、そのデータへの不変参照がある時でさえもデータを可変化できる
各スマートポインタの違い
スマートポインタ | 所有者 | 可変性 | 精査タイミング |
---|---|---|---|
Rc<T> |
複数 | 不変借用のみ | コンパイル時 |
Box<T> |
単一 | 可不変の借用 | コンパイル時 |
RefCell<T> |
単一 | 可不変の借用 | 実行時 |
RefCell
は実行時に精査される可変借用を許可するので、RefCell が不変でも、 RefCell 内の値を可変化できる。
内部可変性パターン
不変な値の中の値を可変化することは、内部可変性パターンです。
不変値への可変借用
Rustでは借用規則により、不変値x
を可変借用&mut
できない。不変として宣言したときと矛盾するから。
main.rs
fn main() { let x = 5; let y = &mut x; // error[E0596]: cannot borrow immutable local variable `x` as mutable }
内部可変のユースケース
ですが、メソッド内で値が自身を可変化するけれども、他のコードにとっては、 不変に見えることが有用な場面もあります。
たとえばテスト時のモックオブジェクト。コード実行時は不変だが、テスト時には可変にしたいとき。
例(RefCell
未使用)
src/lib.rs
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 { self.messenger.send("警告: 割り当ての75%以上を使用してしまいました!"); } else if percentage_of_max >= 0.9 && percentage_of_max < 1.0 { self.messenger.send("切迫した警告: 割り当ての90%以上を使用してしまいました!"); } else if percentage_of_max >= 1.0 { self.messenger.send("エラー: 割り当てを超えています!"); } } }
src/lib.rs
#[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); } }
$ cargo test ... error[E0596]: cannot borrow `self.sent_messages` as mutable, as it is behind a `&` reference --> src/lib.rs:43:13 | 42 | fn send(&self, message: &str) { | ----- help: consider changing this to be a mutable reference: `&mut self` 43 | self.sent_messages.push(String::from(message)); | ^^^^^^^^^^^^^^^^^^ `self` is a `&` reference, so the data it refers to cannot be borrowed as mutable
「不変なフィールドsent_messages
を可変で借用できない」と怒られる。
send
メソッドの第一引数を&mut
にもできない。
impl Messenger for MockMessenger { fn send(&mut self, message: &str) { // error[E0053]: method `send` has an incompatible type for trait
「トレイトの定義と一致しない」と怒られる。トレイトの定義を確認するとsend
メソッドの第一引数は&self
である。&mut self
ではない。よって上記エラー。
pub trait Messenger { fn send(&self, msg: &str);
エラーの詳細を見てみるとヘルプで「トレイトのほうで型を可変にしろ」とある。
error[E0053]: method `send` has an incompatible type for trait --> src/lib.rs:42:17 | 2 | fn send(&self, msg: &str); | ----- type in trait ... 42 | fn send(&mut self, message: &str) { | ^^^^^^^^^ types differ in mutability | = note: expected type `fn(&tests::MockMessenger, &str)` found type `fn(&mut tests::MockMessenger, &str)` help: consider change the type to match the mutability in trait | 42 | fn send(&self, message: &str) { | ^^^^^
これに従ってしまうと「実行時は不変にする」ことができなくなる。テストのためだけに本番のコードを可変に変えてしまう。そんな馬鹿げた事態になる。
試しにやってみると以下のようになる。
pub trait Messenger { fn send(&mut self, msg: &str); } pub struct LimitTracker<'a, T: 'a + Messenger> { messenger: &'a mut T, value: usize, max: usize, } impl<'a, T> LimitTracker<'a, T> where T: Messenger { pub fn new(messenger: &mut 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 { self.messenger.send("警告: 割り当ての75%以上を使用してしまいました!"); } else if percentage_of_max >= 0.9 && percentage_of_max < 1.0 { self.messenger.send("切迫した警告: 割り当ての90%以上を使用してしまいました!"); } else if percentage_of_max >= 1.0 { self.messenger.send("エラー: 割り当てを超えています!"); } } } #[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(&mut self, message: &str) { // error[E0053]: method `send` has an incompatible type for trait // fn send(&self, message: &str) { self.sent_messages.push(String::from(message)); } } #[test] fn it_sends_an_over_75_percent_warning_message() { let mut mock_messenger = MockMessenger::new(); let mut limit_tracker = LimitTracker::new(&mut mock_messenger, 100); limit_tracker.set_value(80); assert_eq!(mock_messenger.sent_messages.len(), 1); } }
たしかに動く。でも不変にしておき、テストのときだけ可変にしたい。それをRefCell<T>
で実装してみる。
例(RefCell
)
不変および可変参照を作成するとき、それぞれ&
と&mut
記法を使う。RefCell<T>では
、 borrow
とborrow_mut
メソッドを使用する。
可変性 | 記法 | RefCell<T> |
---|---|---|
不 | & |
borrow() |
可 | &mut |
borrow_mut() |
src/lib.rs
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 { self.messenger.send("警告: 割り当ての75%以上を使用してしまいました!"); } else if percentage_of_max >= 0.9 && percentage_of_max < 1.0 { self.messenger.send("切迫した警告: 割り当ての90%以上を使用してしまいました!"); } else if percentage_of_max >= 1.0 { self.messenger.send("エラー: 割り当てを超えています!"); } } } #[cfg(test)] mod tests { use super::*; use std::cell::RefCell; struct MockMessenger { sent_messages: RefCell<Vec<String>>, } impl MockMessenger { fn new() -> MockMessenger { MockMessenger { sent_messages: RefCell::new(vec![]) } } } impl Messenger for MockMessenger { fn send(&self, message: &str) { self.sent_messages.borrow_mut().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.borrow().len(), 1); } }
注目すべきは以下。borrow_mut()
で不変なのに可変借用している。
impl Messenger for MockMessenger { fn send(&self, message: &str) { self.sent_messages.borrow_mut().push(String::from(message));
テストは成功する。
借用規則
ただし、RefCell
も借用規則に従っている。借用規則のうち「可変変数は1つのスコープに1つのみ」である。これに抵触する以下コードは実行時にpanic!
する。
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)); } }
$ cargo test ... ---- tests::it_sends_an_over_75_percent_warning_message stdout ---- thread 'tests::it_sends_an_over_75_percent_warning_message' panicked at 'already borrowed: BorrowMutError', src/libcore/result.rs:997:5 note: Run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
「すでに借用済みです」という実行時エラー。
Rc<T>
+ RefCell<T>
で複数の所有者がいる内部可変性データをつくる
RefCell<T>
はふつうRc<T>
と組合せる。Rc<T>
は不変しか許さない。そこで、Rc<T>
にRefCell<T>
をもたせれば可変性を得られる。
#[derive(Debug)] enum List { Cons(Rc<RefCell<i32>>, Rc<List>), Nil, } use List::{Cons, Nil}; use std::rc::Rc; use std::cell::RefCell; fn main() { let value = Rc::new(RefCell::new(5)); let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil))); let b = Cons(Rc::new(RefCell::new(6)), Rc::clone(&a)); let c = Cons(Rc::new(RefCell::new(10)), Rc::clone(&a)); *value.borrow_mut() += 10; println!("a after = {:?}", a); println!("b after = {:?}", b); println!("c after = {:?}", c); }
$ rustc main.rs ./main pi@raspberrypi:/tmp/work/Rust.Smartpointer.RefCell.20190705114547/src/6 $ ./main a after = Cons(RefCell { value: 15 }, Nil) b after = Cons(RefCell { value: 6 }, Cons(RefCell { value: 15 }, Nil)) c after = Cons(RefCell { value: 10 }, Cons(RefCell { value: 15 }, Nil))
value
を参照部分がすべて変更後の値になっている。つまり可変参照であることが確認できた。
所感
でもRefCell
は実行速度を犠牲にする。はたしてテストのためにそれをすべきかどうかは判断に迷う。
あと、Rc::new(RefCell::new(値))
はネスト深すぎ。Rc::clone()
, RefCell.borrow_mut()
など独自メソッドも覚えねばならないし。
これは理解し使いこなすまでに時間かかりそう。
対象環境
- Raspbierry pi 3 Model B+
- Raspbian stretch 9.0 2018-11-13
- bash 4.4.12(1)-release
- rustc 1.34.2 (6c2484dc3 2019-05-13)
- cargo 1.34.0 (6789d8a0a 2019-04-01)
$ uname -a Linux raspberrypi 4.19.42-v7+ #1219 SMP Tue May 14 21:20:58 BST 2019 armv7l GNU/Linux
前回まで
- Rustを学んでみたい(プログラミング言語)
- Rustの環境構築
- RustでHelloWorld
- Rustの和訳ドキュメント
- Cargoでプロジェクト作成・ビルド・実行
- クレートとは?
- Rustで関数を使ってみる
- Rustでモジュールを使ってみる
- Rustで乱数を生成する(rand)
- Rustで標準入力する(std::io::stdin().read_line())
- RustでMatch判定する(match)
- Rustでprintとread_lineを1行にする方法
- Rustで数当てゲーム
- クレート名にドット.が使えない
- Rustの変数と可変性(let, mut) error[E0384]: cannot assign twice to immutable variable
x
- Rustのimmutable束縛とconst定数の違い
- RustのREPL、evcxrのインストールに失敗した
- Rustでコンパイルするときの変数未使用warningを消す
- Rustの変数(再代入、再宣言(シャドーイング))
- Rustのシャドーイングについて
- イミュータブルについて(副作用)
- Rustの定数(const)
- Rustのデータ型(数値)
- Rustのデータ型(論理)
- Rustのデータ型(文字)
- Rustのデータ型(タプル)
- Rustのデータ型(配列)
- Rustの関数
- Rustのif式
- Rustのくりかえし文(loop)
- Rustのくりかえし文(while)
- Rustのくりかえし文(for)
- Rustの所有権(ムーブ)
- Rustの所有権(関数)
- Rustの所有権(スライス)
- Rustの構造体(定義とインスタンス化)
- Rustの構造体(プログラム例)
- Rustの構造体(メソッド)
- Rustの列挙型(enum)
- Rustの列挙型(enum)
- Rustの列挙型(enum)
- Rustのmatch(制御フロー演算子)
- RustでNULLを扱う(Option, Some, None)
- NULL参照は10億ドルの失敗だった
- Rustの列挙型に独自表示を実装する(E0277 対策 std::fmt::Display 実装)
- RustのIfLet(matchの糖衣構文)
- Rustのプロジェクト構造
- Rustのcargoでライブラリ&テスト(単体、結合)
- Rustのモジュール(mod)
- Rustのモジュール(pub)
- Rustのmod参照方法(
mod 子モジュール名;
,use 要素名;
,extern crate クレート名;
,super
) - Rustのインポートまとめ(Rust2018)
- RustのコレクションVec型
- RustのコレクションString型
- RustのコレクションHashMap型
- Rustのコレクション(練習問題)
- Rustのエラー処理
- Rustのジェネリクス
- Rustのトレイト
- Rustのライフタイム1
- Rustのライフタイム2(構造体の定義)
- Rustのライフタイム3(ライフタイム省略)
- Rustのライフタイム4(impl定義)
- Rustの静的ライフタイム5('static)
- Rustのライフタイム6(ジェネリクス、トレイト境界とともに)
- Rustのテストコードを書く
- Rustのテスト実行
- Rustのテスト体系化
- Rustでコマンドライン引数を受け取る
- Rustのファイル読込
- Rustでリファクタリング(モジュール性とエラー処理の向上)
- Rustでテスト駆動開発
- Rustで環境変数を取得する
- RustでStdErr出力
- Rustのクロージャ
- Rustのイテレータ
- Rustのイテレータ(Minigrep改善)
- Rustのイテレータ(パフォーマンス)
- Rustのイテレータ(Minigrep改善)
- Rustのcargo(ビルドのカスタマイズ)
- Rustのcargo(cargo docでドキュメント生成)
- Rustのエクスポート(pub use)
- Rustのクレートを公開する方法(crates.io)
- Rustのcargoでワークスペースをつくる
- Rustのcargo installでバイナリをインストールする
- Rustのcargoを拡張する方法
- Rustのスマートポインタ
- スマートポインタBox
- Rustのスマートポインタ(Derefトレイト)
- Rustのスマートポインタ(Dropトレイト)
- Rustのスマートポインタ(Rc
)