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
は使わないべきではある。