Rustのinterior mutabilityとCellとRefCell - Programming Rust, 2nd Editionから
Programming Rust, 2nd Edition という本(*1)を少しずつ読んでいます。総ページ数は700ページ超えと,紙の本だと軽く鈍器みがありますが,Rustの基礎がプレーンな英語で書かれていて読みやすい。言うなれば,公式のThe Rust Bookを10倍に噛み砕いて,初学者向けに構成を練り直したという感じ。普段Rustはそこそこ書いてるけれど,雰囲気で使っているなあという人が少し腰を据えて学び直すにも良さそうです。
*1) 邦訳も出ています: プログラミングRust 第2版
本記事は,その中で9章のInterior Mutabilityについてのメモです。
- Interior mutability(内部可変性)とは,全体はimmutable(不変)データでありつつ,その一部をmutable(可変)にすること。
- Rustの標準ライブラリでは,
Cell<T>
とRefCell<T>
の2つの型をinterior mutabilityのために提供している。- ただし,
Cell
とRefCell
はスレッドセーフでないため,マルチスレッドプログラムでは使用できない。
- ただし,
というのがinterior mutabilityとCell
とRefCell
のおそらくよくある大まかな定義(Rust Bookでもだいたいこんな感じで説明されていると思う)で,定義だけだとまあよくわからない。わかるようでピンときていなかったinterior mutabilityが,この本の説明で「あああ,なるほど完全理解」と思えたのが収穫でした。
以下は,interior mutabilityを実例で理解するために自分で書いたコードです。本のサンプルコードとは別物です。
use ;
// 運転免許証を表す構造体
// フィールドとして番号,生年月日,改定番号,氏名,住所をもつ。
// `DriverLicense` は基本的に不変でありながら,`revision`, `name`, `address` の各フィールドは内部可変性を持つ。
/// 住所を表す構造体
// DriverLicenseのインスタンスを生成
// `license`は(基本的に)不変なので,`mut` でないことに注意。
let license = DriverLicense ;
revision
を変更する
// Cell::set() で値を変更
license.revision.set;
println!;
name
を変更する
// RefCell::replace() で値を置換
license.name.replace;
println!;
address
を変更する
// RefCell::borrow_mut() で可変参照を取得して値を変更
let mut addr = license.address.borrow_mut;
addr.zip = "765-4321".to_string;
addr.prefecture = "神奈川県".to_string;
addr.city = "横浜市".to_string;
// panic! since `addr` is already borrowed mutably
println!;
このコードはぱっと見,動きそうですが,最後の行でpanicします。borrow ruleに違反しているため。
RefCell::borrow()
やRefCell::borrow_mut()
のborrow ruleは,コンパイル時ではなく実行時にチェックされるため,うっかりpanicするコードを書いてしまうことがあります。
ということで,RefCell
を使う場合はtry_borrow()
やtry_borrow_mut()
を使って,panicを起こす代わりにエラーを処理するほうが良さそうです。
if let Ok = license.address.try_borrow_mut
if let Ok = license.address.try_borrow