到達点は、共有や内部変更が必要な理由を説明して型を選ぶことです。前提は第05・09回です。
所有権の道具を目的で選ぶ
Boxはヒープ上の値を単独所有する道具、Rcは単一スレッドで参照カウントにより共有所有する道具です。RefCellは借用規則の一部を実行時に確認し、共有参照の内側から変更する「内部可変性」を提供します。
use std::cell::RefCell;
use std::rc::Rc;
fn main() {
let shared = Rc::new(RefCell::new(vec!["基礎".to_owned()]));
let another_owner = Rc::clone(&shared);
{
let mut items = another_owner.borrow_mut();
items.push("演習".to_owned());
}
println!("{:?}", shared.borrow());
println!("{}", Rc::strong_count(&shared));
let weak = Rc::downgrade(&shared);
drop(another_owner);
drop(shared);
println!("{}", weak.upgrade().is_none());
}
src/main.rsの出力は基礎・演習、所有者数2、trueです。Rc::cloneは中身のVecを複製せず、所有者を増やします。可変借用のガードを内側のスコープで落としてから、共有借用しています。
便利さと代償
RefCellで借用が衝突すると、borrowやborrow_mutはpanicします。try_borrow系で失敗を結果として受け取る方法もあります。コンパイラが通ったからあらゆる借用問題が消えたわけではありません。
Rc同士が強い参照で輪を作ると、参照カウントが0にならず解放されない場合があります。親への逆参照などにはWeakを使う設計が候補です。Weakは所有者を生かし続けず、upgradeすると有効な所有者を取得できるかがOptionで分かります。
別スレッドへ共有するならArcが候補ですが、Arcは中身を自動で安全に変更できるようにはしません。変更を共有する場合はMutexなどと組み合わせ、データの型がSendやSyncの条件を満たす必要があります。
練習と解答
練習:なぜこの例ではborrow_mutのスコープを小さくしたのでしょうか。
解答:可変借用のガードを早く解放し、その後の共有借用と重ならないようにするためです。ガードを保持したままshared.borrowを呼べば実行時に衝突します。ロックや借用の保持期間を短くする考え方は、並行処理にもつながります。
公式資料
スマートポインタを参照できます。
Rust全20回の目次 | 前の回 | 次の回