web-dev-qa-db-ja.com

CellまたはRefCellが最良の選択である状況

いつ使用する必要がありますか CellまたはRefCell ?これらの代わりに適したタイプの選択肢は他にもたくさんあるようです。ドキュメントでは、RefCellの使用は少し「最後の手段」であると警告しています。

これらのタイプを使用していますか? " コードの臭い "? RcBoxなど、他のタイプを使用するよりもこれらのタイプを使用する方が理にかなっている例を誰かが示すことができますか?

23
jocull

CellまたはRefCellBoxおよびRcで使用するタイミングを尋ねるのは完全に正しくありません。これらのタイプは、さまざまな問題を解決するからです。実際、共有所有権で可変性を提供するために、RefCellRcと一緒に一緒に使用されることがよくあります。そうです、CellRefCellのユースケースは、コードの可変性要件に完全に依存しています。

内部と外部の可変性は、公式の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は、スレッド間で安全に使用できる内部可変性の別のタイプです。

したがって、ご覧のとおり、CellRefCellRcまたはBoxの代わりにはなりません。それらは、デフォルトで許可されていない場所で可変性を提供するというタスクを解決します。それらをまったく使用せずにコードを書くことができます。そして、あなたがそれらを必要とする状況に陥った場合、あなたはそれを知るでしょう。

CellsとRefCellsはコードの臭いではありません。 「最後の手段」と説明されている唯一の理由は、RefCellの場合のように、可変性とエイリアスのルールをチェックするタスクをコンパイラからランタイムコードに移動することです。2つの_&mut_を指すことはできません。同時に同じデータに対して、これはコンパイラによって静的に適用されますが、RefCellsを使用すると、同じRefCellに、必要な数の_&mut_を指定するように要求できます。ただし、複数回実行する場合を除きます。実行時にエイリアスルールを適用して、パニックになります。パニックは、コンパイル時ではなく実行時にのみ発生するエラーを見つけることができるため、コンパイルエラーよりも間違いなく悪いです。ただし、コンパイラーの静的アナライザーの制限が厳しすぎる場合があり、実際にそれを「回避」する必要があります。

33

いいえ、CellRefCellは「コードの臭い」ではありません。通常、可変性は継承です。つまり、全体の排他的アクセス権がある場合にのみ、フィールドまたはデータ構造の一部を可変化できます。データ構造、したがって、mut(つまり、foo.xinheritsその可変性または)を使用して、そのレベルで可変性を選択できます。 fooからのその欠如)。これは非常に強力なパターンであり、うまく機能する場合はいつでも使用する必要があります(驚くほど頻繁に使用されます)。しかし、それはあらゆる場所のすべてのコードに対して十分に表現力がありません。

BoxRcはこれとは何の関係もありません。他のほとんどすべてのタイプと同様に、継承された可変性を尊重します。Boxへの排他的で可変のアクセス権がある場合は、Boxのコンテンツを変更できます(つまり、コンテンツへの排他的アクセス権があるため) 、も)。逆に、Rcはその性質上共有されているため(つまり、複数のRcが存在する可能性があるため、Rcの内容に&mutを取得することはできません。同じデータ)。

CellまたはRefCellの一般的なケースの1つは、複数の場所間で可変デー​​タを共有する必要がある場合です。同じデータへの2つの&mut参照を持つことは、通常は許可されていません(そして正当な理由があります!)。ただし、場合によっては必要それがあり、セルタイプによって安全に実行できます。

これは、Rc<RefCell<T>>の一般的な組み合わせを介して行うことができます。これにより、誰もがデータを使用している限りデータを保持し、全員(一度に1つだけ!)がデータを変更できます。または、&Cell<i32>のように単純な場合もあります(セルがより意味のあるタイプでラップされている場合でも)。後者は、参照カウントのような内部、プライベート、可変状態にも一般的に使用されます。

ドキュメントには、実際にはCellまたはRefCellを使用する場所の例がいくつかあります。良い例は、実際にはRc自体です。新しいRcを作成するときは、参照カウントを増やす必要がありますが、参照カウントはすべてのRcで共有されるため、継承された可変性により、これは機能しない可能性があります。 Rc実質的にCellを使用する必要があります

良いガイドラインは、セルタイプなしでできるだけ多くのコードを書いてみることですが、セルタイプなしではあまりにも痛いときにそれらを使用します。場合によっては、セルなしで良い解決策があり、経験を積むと、以前にセルを見逃したときにそれらを見つけることができますが、セルなしでは不可能なことが常にあります。

10
user395760

選択したタイプのオブジェクトを作成し、それをRcにダンプする必要があるとします。

let x = Rc::new(5i32);

これで、まったく同じオブジェクト、つまりメモリ位置を指す別のRcを簡単に作成できます。

let y = x.clone();
let yval: i32 = *y;

Rustでは、他の参照が存在するメモリ位置への変更可能な参照がない可能性があるため、これらのRcコンテナを再度変更することはできません。

では、これらのオブジェクトを変更できるようにしたい場合はどうでしょうかand 1つの同じオブジェクトを指す複数のRcがありますか?

これは、CellRefCellが解決する問題です。このソリューションは「内部可変性」と呼ばれ、Rustのエイリアシングルールがコンパイル時ではなく実行時に適用されることを意味します。

元の例に戻ります。

let x = Rc::new(RefCell::new(5i32));
let y = x.clone();

タイプへの変更可能な参照を取得するには、RefCellborrow_mutを使用します。

let yval = x.borrow_mut();
*yval = 45;

Rcsが可変または非可変のいずれかで指す値を既に借用している場合、borrow_mut関数はパニックになり、Rustのエイリアスルールが適用されます。

Rc<RefCell<T>>RefCellのほんの一例であり、他にも多くの正当な用途があります。しかし、ドキュメントは正しいです。別の方法がある場合は、それを使用してください。コンパイラーはRefCellsについて推論するのに役立ちません。

7
oli_obk