RefCell<T> / Cell<T>
| 対応: | Rust 1.0(2015) |
|---|
『RefCell<T>』と『Cell<T>』は内部可変性を提供するスマートポインタで、通常のRustの借用ルールを実行時にチェックすることで、不変参照を通じた値の変更を可能にします。
構文
use std::cell::{Cell, RefCell};
// RefCell:Copyでない型(String, Vec等)に使います。
let r = RefCell::new(値);
let borrowed = r.borrow(); // 不変借用(&T)
let mut borrowed = r.borrow_mut(); // 可変借用(&mut T)
// Cell:Copyトレイトを持つ型(i32, bool等)に使います。
let c = Cell::new(値);
c.set(新しい値);
c.get();
メソッド一覧
| メソッド | 概要 |
|---|---|
| RefCell::new(v) | 値vをRefCellでラップします。 |
| borrow() | 不変借用(Ref<T>)を返します。可変借用中はpanic!します。 |
| borrow_mut() | 可変借用(RefMut<T>)を返します。他の借用中はpanic!します。 |
| try_borrow() | 借用に失敗してもpanic!せずResult<Ref<T>, BorrowError>を返します。 |
| try_borrow_mut() | 可変借用をResult型で返します。失敗してもpanic!しません。 |
| Cell::new(v) | Copyトレイトのある値をCellでラップします。 |
| cell.get() | 値のコピーを返します(Copyトレイトが必要)。 |
| cell.set(v) | 値を置き換えます。借用なしで変更できます。 |
サンプルコード
sample_refcell_cell.rs
use std::cell::{Cell, RefCell};
// 不変参照でも内部の値を変更できる構造体
struct Counter {
count: Cell<u32>,
log: RefCell<Vec<String>>,
}
impl Counter {
fn new() -> Self {
Counter {
count: Cell::new(0),
log: RefCell::new(vec![]),
}
}
// &self(不変参照)でも内部を変更できます。
fn increment(&self, label: &str) {
let n = self.count.get() + 1;
self.count.set(n);
self.log.borrow_mut().push(format!("{}: {}", label, n));
}
fn show(&self) {
for entry in self.log.borrow().iter() {
println!("{}", entry);
}
}
}
fn main() {
let c = Counter::new();
c.increment("a"); // a: 1
c.increment("b"); // b: 2
c.increment("c"); // c: 3
c.show();
println!("total: {}", c.count.get()); // total: 3
}
rustc refcell_cell.rs ./refcell_cell a: 1 b: 2 c: 3 total: 3
refcell_cell2.rs — try_borrow / try_borrow_mut でpanicを回避する
use std::cell::RefCell;
fn main() {
let data = RefCell::new(vec!["Kogami Shinya", "Tsunemori Akane"]);
// borrow中にtry_borrow_mutを試みるとErrが返ります(panicしません)。
let borrowed = data.borrow(); // 不変借用を取得します。
println!("current: {:?}", *borrowed);
match data.try_borrow_mut() {
Ok(mut v) => v.push("Ginoza Nobuchika"),
Err(e) => println!("借用中のためmut借用不可: {}", e),
}
drop(borrowed); // 不変借用を手動で解放します。
// 解放後はtry_borrow_mutが成功します。
if let Ok(mut v) = data.try_borrow_mut() {
v.push("Masaoka Tomomi");
println!("追加後: {:?}", *v);
}
}
rustc refcell_cell2.rs ./refcell_cell2 current: ["Kogami Shinya", "Tsunemori Akane"] 借用中のためmut借用不可: already borrowed: BorrowMutError 追加後: ["Kogami Shinya", "Tsunemori Akane", "Masaoka Tomomi"]
refcell_cell3.rs — Cell<T>:Copy型の軽量な内部可変性
use std::cell::Cell;
struct Fighter {
name: &'static str,
power: Cell<u32>, // Copy型なのでCellで十分です。
is_active: Cell<bool>,
}
impl Fighter {
fn new(name: &'static str, power: u32) -> Self {
Fighter {
name,
power: Cell::new(power),
is_active: Cell::new(true),
}
}
fn power_up(&self, amount: u32) {
self.power.set(self.power.get() + amount);
}
fn retire(&self) {
self.is_active.set(false);
}
}
fn main() {
let f = Fighter::new("Vegeta", 8000); // 不変バインディングです。
f.power_up(1000);
f.power_up(500);
println!("{}: power={}, active={}", f.name, f.power.get(), f.is_active.get());
f.retire();
println!("{}: active={}", f.name, f.is_active.get());
}
rustc refcell_cell3.rs ./refcell_cell3 Vegeta: power=9500, active=true Vegeta: active=false
概要
Rustのコンパイル時借用チェックは強力ですが、設計上どうしても不変参照から内部を変更したいケースがあります。例えば『Rc<T>』で共有している値を変更したい場合、通常の『&mut』は使えません。このような場面で『RefCell<T>』による内部可変性が必要になります。『RefCell<T>』と『Cell<T>』はそのような「内部可変性」を提供し、借用ルールはコンパイル時ではなく実行時にチェックされます。
『Cell』はCopyトレイトを持つ軽量な型に使い、『RefCell』はStringやVecなどCopy不可の型に使います。どちらもシングルスレッド専用です。
RefCellの借用ルール違反(同時に可変借用と不変借用を取るなど)はコンパイルエラーではなく実行時panic!になります。境界が分かりにくい場合は安全な『try_borrow()』系メソッドを使ってください。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。