Rustで参照から可変参照を拝借するにはワイルドに unsafeを使うことになるんだけど(悪かった自慢)、Clippyにかけたら未定義動作だと言って怒られたので修正する。
未定義動作版
以前状態管理や相互参照をどうするかで行ったpeep関数:
pub unsafe fn peep<'a, T: ?Sized>(t: &T) -> &'a mut T { |
をClippyにかけるとエラーが出る:
error: casting `&T` to `&mut T` may cause undefined behavior, consider instead using an `UnsafeCell` |
&T から &mut T へのキャストは未定義動作なので、代わりにUnsafeCellを使え、と言われている。
UnsafeCell経由版
UnsafeCell を使えとのことだけど selfの可変参照を(コンパイラを騙して)取得したいので、UnsafeCell として保持しておくわけにはいかなかった。
なので参照→UnsafeCell→可変参照とキャストしてやる:
use std::cell::UnsafeCell; |
コードは UnsafeCell in std::cell - Rust のメモリレイアウトの節のget_mut()とget_shared()を参考にした。
UnsafeCell<T>はその内部型Tと同じメモリ内表現を持ちます。 この保証の結果、TからUnsafeCell<T>への変換が可能になります。
共有の
UnsafeCell<T>の中身への*mut Tポインタを獲得する唯一の有効な方法は.get()か.raw_get()を使用する方法だということに注意してください。&mut T参照を獲得するには、このポインタをデリファレンスするか、 排他的なUnsafeCell<T>に対する.get_mut()を呼び出すかのどちらかです。
本来のUnsafeCellの使い方は?
正確に理解できているとは言い難いんだけど上記でコンパイラやClippyに怒られないのは騙しているだけで、競合による危険性は変わりはない(メモリレイアウトが保証されてるので未定義動作ではないという程度で)。
UnsafeCellは本来は「内部可変性」、外には不変に見せる(なので不変参照で複数に共有できる)けど内部では内容を書き換える、という用途に使用する。
例えばlet t: UnsafeCell<T> = ...;というふうに所有、不変の借用&UnsafeCell<T>が共有できて、その上で内部可変性で裏で&mut Tに変換して内容を書き換える、ということが未定義動作ではなくできるよ、ということだと思う
(その上で安全性は自分で保証する必要がある)。
let t = UnsafeCell::new(0i32); |
まあ&UnsafeCellで受け渡すのもアレなので構造体struct Foo{}などで包んだ&Foo で受け渡して、
impl Foo{} 内部のメソッドで密かに内部を変更するというのが筋だろうと思う:
struct Foo { |
さらにはCellやRefCellを使って、UnsafeCellは使わないべきではある。
- UnsafeCell in std::cell - Rust
- RefCell
と内部可変性パターン - The Rust Programming Language 日本語版 - 内部可変性|Rust入門
- 危険な型変換:&T→&mut T - yohhoyの日記