MaybeUninitのassume_initメソッドについて調べたのでまとめた (Rust)

前に書いた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 1216Rust 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周りを理解していないなと気づきました。writeDropも調べて、また今度まとめようと思います。

*1:モジュールの関数にアクセスする"::"と相まってちょっと慣れないところがあります...。

*2:MaybeUninitで初期化していない参照を扱う (Rust)を書いた時は知りませんでした。

*3:共有型だったんですね。全然意識していなかった。

*4:要素のないタプルもしくはユニット型と呼ばれます

*5:うーん、ちょっとココらへんも中身を追わないとわからないです