いつ使用する必要がありますか CellまたはRefCell ?これらの代わりに適したタイプの選択肢は他にもたくさんあるようです。ドキュメントでは、RefCell
の使用は少し「最後の手段」であると警告しています。
これらのタイプを使用していますか? " コードの臭い "? Rc
やBox
など、他のタイプを使用するよりもこれらのタイプを使用する方が理にかなっている例を誰かが示すことができますか?
Cell
またはRefCell
をBox
およびRc
で使用するタイミングを尋ねるのは完全に正しくありません。これらのタイプは、さまざまな問題を解決するからです。実際、共有所有権で可変性を提供するために、RefCell
がRc
と一緒に一緒に使用されることがよくあります。そうです、Cell
とRefCell
のユースケースは、コードの可変性要件に完全に依存しています。
内部と外部の可変性は、公式のRustの本、 可変性に関する指定された章 で非常にうまく説明されています。外部の可変性は所有権モデルと非常に密接に関連しており、ほとんどの場合何かが可変または不変であると言うときは、正確に外部可変性を意味します。外部可変性の別名はinherited可変性であり、おそらく概念をより明確に説明しています。 :この種の可変性は、データの所有者によって定義され、所有者から到達できるすべてのものに継承されます。たとえば、構造タイプの変数が可変である場合、変数内の構造のすべてのフィールドも可変です。
_struct Point { x: u32, y: u32 }
// the variable is mutable...
let mut p = Point { x: 10, y: 20 };
// ...and so are fields reachable through this variable
p.x = 11;
p.y = 22;
let q = Point { x: 10, y: 20 };
q.x = 33; // compilation error
_
継承された可変性は、値から取得できる参照の種類も定義します。
_{
let px: &u32 = &p.x; // okay
}
{
let py: &mut u32 = &mut p.x; // okay, because p is mut
}
{
let qx: &u32 = &q.x; // okay
}
{
let qy: &mut u32 = &mut q.y; // compilation error since q is not mut
}
_
ただし、継承された可変性では不十分な場合があります。正規の例は、RustではRc
と呼ばれる参照カウントポインタです。次のコードは完全に有効です。
_{
let x1: Rc<u32> = Rc::new(1);
let x2: Rc<u32> = x1.clone(); // create another reference to the same data
let x3: Rc<u32> = x2.clone(); // even another
} // here all references are destroyed and the memory they were pointing at is deallocated
_
一見、可変性がこれにどのように関連しているかは明らかではありませんが、参照が複製されると変更される内部参照カウンターが含まれているため、参照カウントポインターが呼び出されることを思い出してください(Rustのclone()
)および破棄されます(Rust
のスコープ外になります)。したがって、Rc
は、非mut
変数内に格納されている場合でも、それ自体を変更する必要があります。
これは、内部の可変性によって実現されます。標準ライブラリには特別なタイプがあり、その中で最も基本的なものは UnsafeCell
です。これにより、外部の可変性のルールを回避し、非-に(推移的に)格納されている場合でも何かを変更できます。 mut
変数。
何かが内部可変性を持っていると言う別の言い方は、これは_&
_-参照を介して変更できるということです-つまり、タイプ_&T
_の値があり、T
の状態を変更できる場合それが指すと、T
には内部可変性があります。
たとえば、Cell
にはCopy
データを含めることができ、mut
以外の場所に格納されている場合でも、変更することができます。
_let c: Cell<u32> = Cell::new(1);
c.set(2);
assert_eq!(c.get(), 2);
_
RefCell
には、Copy
以外のデータを含めることができ、含まれている値への_&mut
_ポインターを提供でき、実行時にエイリアシングがないかどうかがチェックされます。これはすべて、ドキュメントページで詳細に説明されています。
結局のところ、圧倒的な数の状況では、外部の可変性だけで簡単に行くことができます。 Rustの既存の高レベルコードのほとんどはそのように記述されています。ただし、内部の可変性が避けられない場合や、コードがはるかに明確になる場合があります。1つの例、Rc
実装については、すでに上記で説明しています。 1つは、共有の可変所有権が必要な場合(つまり、コードの異なる部分から同じ値にアクセスして変更する必要がある場合)です。これは、参照だけでは実行できないため、通常は_Rc<RefCell<T>>
_を介して実現されます。 。さらに別の例は_Arc<Mutex<T>>
_であり、Mutex
は、スレッド間で安全に使用できる内部可変性の別のタイプです。
したがって、ご覧のとおり、Cell
とRefCell
はRc
またはBox
の代わりにはなりません。それらは、デフォルトで許可されていない場所で可変性を提供するというタスクを解決します。それらをまったく使用せずにコードを書くことができます。そして、あなたがそれらを必要とする状況に陥った場合、あなたはそれを知るでしょう。
Cell
sとRefCell
sはコードの臭いではありません。 「最後の手段」と説明されている唯一の理由は、RefCell
の場合のように、可変性とエイリアスのルールをチェックするタスクをコンパイラからランタイムコードに移動することです。2つの_&mut
_を指すことはできません。同時に同じデータに対して、これはコンパイラによって静的に適用されますが、RefCell
sを使用すると、同じRefCell
に、必要な数の_&mut
_を指定するように要求できます。ただし、複数回実行する場合を除きます。実行時にエイリアスルールを適用して、パニックになります。パニックは、コンパイル時ではなく実行時にのみ発生するエラーを見つけることができるため、コンパイルエラーよりも間違いなく悪いです。ただし、コンパイラーの静的アナライザーの制限が厳しすぎる場合があり、実際にそれを「回避」する必要があります。
いいえ、Cell
とRefCell
は「コードの臭い」ではありません。通常、可変性は継承です。つまり、全体の排他的アクセス権がある場合にのみ、フィールドまたはデータ構造の一部を可変化できます。データ構造、したがって、mut
(つまり、foo.x
inheritsその可変性または)を使用して、そのレベルで可変性を選択できます。 foo
からのその欠如)。これは非常に強力なパターンであり、うまく機能する場合はいつでも使用する必要があります(驚くほど頻繁に使用されます)。しかし、それはあらゆる場所のすべてのコードに対して十分に表現力がありません。
Box
とRc
はこれとは何の関係もありません。他のほとんどすべてのタイプと同様に、継承された可変性を尊重します。Box
への排他的で可変のアクセス権がある場合は、Box
のコンテンツを変更できます(つまり、コンテンツへの排他的アクセス権があるため) 、も)。逆に、Rc
はその性質上共有されているため(つまり、複数のRc
が存在する可能性があるため、Rc
の内容に&mut
を取得することはできません。同じデータ)。
Cell
またはRefCell
の一般的なケースの1つは、複数の場所間で可変データを共有する必要がある場合です。同じデータへの2つの&mut
参照を持つことは、通常は許可されていません(そして正当な理由があります!)。ただし、場合によっては必要それがあり、セルタイプによって安全に実行できます。
これは、Rc<RefCell<T>>
の一般的な組み合わせを介して行うことができます。これにより、誰もがデータを使用している限りデータを保持し、全員(一度に1つだけ!)がデータを変更できます。または、&Cell<i32>
のように単純な場合もあります(セルがより意味のあるタイプでラップされている場合でも)。後者は、参照カウントのような内部、プライベート、可変状態にも一般的に使用されます。
ドキュメントには、実際にはCell
またはRefCell
を使用する場所の例がいくつかあります。良い例は、実際にはRc
自体です。新しいRc
を作成するときは、参照カウントを増やす必要がありますが、参照カウントはすべてのRc
で共有されるため、継承された可変性により、これは機能しない可能性があります。 Rc
実質的にはCell
を使用する必要があります。
良いガイドラインは、セルタイプなしでできるだけ多くのコードを書いてみることですが、セルタイプなしではあまりにも痛いときにそれらを使用します。場合によっては、セルなしで良い解決策があり、経験を積むと、以前にセルを見逃したときにそれらを見つけることができますが、セルなしでは不可能なことが常にあります。
選択したタイプのオブジェクトを作成し、それをRc
にダンプする必要があるとします。
let x = Rc::new(5i32);
これで、まったく同じオブジェクト、つまりメモリ位置を指す別のRc
を簡単に作成できます。
let y = x.clone();
let yval: i32 = *y;
Rustでは、他の参照が存在するメモリ位置への変更可能な参照がない可能性があるため、これらのRc
コンテナを再度変更することはできません。
では、これらのオブジェクトを変更できるようにしたい場合はどうでしょうかand 1つの同じオブジェクトを指す複数のRc
がありますか?
これは、Cell
とRefCell
が解決する問題です。このソリューションは「内部可変性」と呼ばれ、Rustのエイリアシングルールがコンパイル時ではなく実行時に適用されることを意味します。
元の例に戻ります。
let x = Rc::new(RefCell::new(5i32));
let y = x.clone();
タイプへの変更可能な参照を取得するには、RefCell
でborrow_mut
を使用します。
let yval = x.borrow_mut();
*yval = 45;
Rc
sが可変または非可変のいずれかで指す値を既に借用している場合、borrow_mut
関数はパニックになり、Rustのエイリアスルールが適用されます。
Rc<RefCell<T>>
はRefCell
のほんの一例であり、他にも多くの正当な用途があります。しかし、ドキュメントは正しいです。別の方法がある場合は、それを使用してください。コンパイラーはRefCell
sについて推論するのに役立ちません。