RustのUnsafeCellとFreeze

概要: UnsafeCell の存在目的について説明する。

UnsafeCell とは

UnsafeCell とは、その名前の通り、内部可変性(interior mutability)を実現するためのunsafeはプリミティブである。

UnsafeCell の代表的な用途は内部可変性を実現するための安全なラッパーであるCellRefCell である。それらの他に、並行性を扱うモジュール(std::sync, std::thread, std::sys)の内部で使われている。

UnsafeCell 自体は以下のように単なるラッパー構造体である

pub struct UnsafeCell<T: ?Sized> {
    value: T,
}

したがって UnsafeCell の特異性はコンパイラによる特殊な扱いに由来することになる。

UnsafeCell の扱い

UnsafeCell は主に以下の点で特殊である。

Freezecore::marker 内に定義されているプライベートなマーカートレイトである。その定義は以下のようになっている。(1.17.0時点では Freeze はまだ存在せず、コンパイラに組込みのフラグで管理されている。)

#[lang = "freeze"]
unsafe trait Freeze {}

unsafe impl Freeze for .. {}

impl<T: ?Sized> !Freeze for UnsafeCell<T> {}
unsafe impl<T: ?Sized> Freeze for PhantomData<T> {}
unsafe impl<T: ?Sized> Freeze for *const T {}
unsafe impl<T: ?Sized> Freeze for *mut T {}
unsafe impl<'a, T: ?Sized> Freeze for &'a T {}
unsafe impl<'a, T: ?Sized> Freeze for &'a mut T {}

impl Tr for .. {} については過去の記事を参照。これにより、Freezeはおよそ「UnsafeCell をメンバとして持たない型」として定義されることになる。 ([T; 0] という例外はある。)

Freeze の扱い

Freeze は主に次のような役割をもつ。

なお、「エイリアスを持たない」というのは正確にいうと全く正しくないが、LLVMの最適化フラグとしてのnoaliasは、「エイリアスによるデータ競合が発生しない」という意味であるため、問題ない。

逆に、真にエイリアスを持たないはずの &mut にはnoaliasがついていない。これはLLVMの最適化バグへのワークアラウンドによるものである。

まとめ

mut がついていないのにメモリ領域が書き換えられる可能性がある場合は、該当部分を UnsafeCell でラップする必要がある。通常は UnsafeCellCellRefCell などのより高級なライブラリを経由して呼び出されるので、この内容をより具体的に把握する必要はない。