前に書いたMaybeUninitで初期化していない参照を扱う (Rust)で気になっていたassume_init()
を調べたメモです。
気になっていた部分は下記です。
pub unsafe fn assume_init(self) -> T { intrinsics::panic_if_uninhabited::<T>(); ManuallyDrop::into_inner(self.value) }
panic_if_uninhabited()
panic_if_uninhabited()はstd::intrinsics
モジュールにあるコンパイラ組み込みの関数なようです。
panic_if_uninhabited::<T>()
の::<T>
はpanic_if_uninhabited()がジェネリクス関数の時に型を与えて関数呼び出しをする書き方です*1。
panic_if_uninhabited()を入れることにより、Tがuninhabitedだとpanicを起こします。そうでなければ何もしません。使い所は、unsafeな関数でTがuninhabitedだと実行できないような時に使うと良いようです。
uninhabitedとはどんな型でしょうか?
uninhabited types
Rust RFC 1216やRust RFC 1892に書いてありました。1892の方はMaybeUninit
に関わるRFCです*2。
uninhabited typesとはどうやら!
のことのようです。これは何もないことを表す型です。例えばexit()は
pub fn exit(code: i32) -> !
というように戻り値がないことを!
型として関数定義されています。
ということで、panic_if_uninhabited()により!
型の場合はPanicを起こすようになっているということでした。
panic_if_uninhabited()が必要な理由を説明します。
MaybeUninit共有型
MaybeUninit共有型は以下のように定義されています*3。
pub union MaybeUninit<T> { uninit: (), value: ManuallyDrop<T>, }
初期化されていない場合は()
*4が共有型の持つ値になります。初期化されている場合に保持する値はManuallyDrop
構造体がその値を持つようになっています。assume_init()
により内部で保持しているT
を取得できます。!
を返すということは起こり得ないのでPanicにしているという感じでしょうか。
assume_init()ではpanic_if_uninhabited()で何も起こらない場合は次にManuallyDrop::into_inner(self.value)
を実行します。続いて、ManuallyDrop構造体について見ていきます。
ManuallyDrop構造体
ManuallyDrop構造体はコンパイラに自動で値の破棄をさせたくない場合に使います。定義では以下のようになっています。
pub struct ManuallyDrop<T: ?Sized> { value: T, }
valueとしてT型の値を単純に持っているだけのようです。構造体を作成する時はnew()
を呼び出します。
pub const fn new(value: T) -> ManuallyDrop<T> { ManuallyDrop { value } }
ただ、MaybeUninitで初期化していない参照を扱う (Rust)では、以下のようにメモリに直接書き込んでいました。
out.write(vec![1, 2, 3]);
これができるのはMaybeUninitが共有型だからということでしょうか*5。MaybeUninitでは直接メモリに書き込んで初期化することがあります。そのような場合はコンパイラにより自動破棄されないのでManuallyDrop型がちょうど良いです。当たり前ですがManually dropなものを扱う時はManuallyDrop型なんですね。
into_inner()
ManuallyDrop構造体のinto_innerメソッドの定義は次のようになっています。
pub const fn into_inner(slot: ManuallyDrop<T>) -> T { slot.value }
引数に与えられたManuallyDrop構造体のvalueを取り出しているだけです。これにより中の値がMoveされ、コンパイラにより自動破棄されるようになるということですね。
おわりに
RustのDestructors周りを理解していないなと気づきました。writeやDropも調べて、また今度まとめようと思います。