RefCellとは
スマートポインタの1つで、保持しているデータに対して、借用ルールを実行時に移します。
借用ルールとRefCell
前述のようにRefCellは借用ルールと関わりがあります。
まずは借用ルールについて簡単に説明し、実際に借用ルールを破ってみます。
その後、RefCellで借用ルールを実行時に移すとどうなるかをみていきます。
借用ルールについて
借用ルールに関しては、以下の記事に詳しく記載しています。
重要な点は、「不変参照をたくさんor可変参照1つ」が借用ルールだということです。
借用ルール違反例
借用ルールを破るとどうなるのでしょうか。
実際に借用のルールを破ってみます。
以下のようなコードで借用ルールを破ることができます。
可変参照を1つ、不変参照を1つ作るため、借用ルールに違反しています。
fn main() {
println!("compiled successfully");
let mut x = 5;
let rx = &mut x; // 可変参照
let rx1 = &x; // 不変参照
println!("{}",rx1);
println!("{}",rx);
}
結果はコンパイルエラーでした。
$ cargo run
Compiling refcell v0.1.0 (/Users/fukuda/ghq/github.com/kfuku1634/practice-rust/refcell)
error[E0502]: cannot borrow `x` as immutable because it is also borrowed as mutable
--> src/main.rs:6:15
|
5 | let rx = &mut x;
| ------ mutable borrow occurs here
6 | let rx1 = &x;
| ^^ immutable borrow occurs here
7 | println!("{}",rx1);
8 | println!("{}",rx);
| -- mutable borrow later used here
For more information about this error, try `rustc --explain E0502`.
error: could not compile `refcell` (bin "refcell") due to previous error
RefCellを使用して借用ルールを実行時に移す
今度はRefCellを使用してみます。
RefCellを使用して、借用ルールを破るコードを書くと、借用ルールが実行時に移るのでコンパイル、ビルドはできるはずです。
それでは、実際にRefCellを使って借用ルールを破ってみます。
RefCellを用いた借用ルール違反例
以下のようなコードを書いて借用ルールを破ってみます。
use std::cell::RefCell;
fn main() {
println!("compiled successfully");
let x = RefCell::new(5);
let rx = x.borrow_mut(); //可変参照
let rx1 = x.borrow(); //不変参照
println!("{}",rx1);
println!("{}",rx);
}
結果、panicしました。
mainの最初のprintである「compiled successfully」は出力されました。
8行目の不変参照を作ったところで、panicします。
RefCellで可変参照と不変参照の数をカウントしており、可変参照があるのに不変参照を作ろうとするとpanicになるようです。
$ cargo run
Compiling refcell v0.1.0 (/Users/fukuda/ghq/github.com/kfuku1634/practice-rust/refcell)
Finished dev [unoptimized + debuginfo] target(s) in 0.47s
Running `target/debug/refcell`
compiled successfully
thread 'main' panicked at 'already mutably borrowed: BorrowError', src/main.rs:8:17
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
RefCell使用の実例
AtCoderで精進しているときに、RefCellを使用するタイミングに出会いました。
こちらの問題です。
https://atcoder.jp/contests/abc353/tasks/abc353_e
公式の解説に従ってTrie木を実装している時に使用しました。
提出コードはこちらです。
Trie木にansを持って、その値を更新しています。
最初からRefCellを使おうとは思っていませんでした。
ansをusizeで持って、処理を行おうとすると、以下のようなステップでコンパイルエラーになってしまいます。
- ansをusizeで持って値を更新しようとすると、solve(&self)をsolve(&mut self)に変更する必要あり。つまり、selfは可変参照である必要がある。
- 現在のノードから次のノードを探索するためにイテレータとして不変参照を1つ持っている。
- solveを再帰的に呼び出すため、可変参照を渡す必要がある。ここでルール違反になる。
RefCellを使うと、以下kのようなステップでpanicを起こさず実行できます。
- ansをRefCellで持つ。
- solveメソッドのselfは不変参照で良い。
- 現在のノードから次のノードを探索するためにイテレータとして不変参照を1つ持っている。
- solveも不変参照。
- ans更新時にRefCellで一時的にansの可変参照を取得。ansはここでしか参照していないので、実行時のpanicも起こらない。
借用ルールに困ったら「RefCellが使えるかも」と思い出せるようになると、さらにRustが書きやすくなるかもしれません。
コメント