MaybeUninitで初期化していない参照を扱う (Rust)

はじめに

あるコードを見ていた時にstd::mem::uninitializedを見つけました。これはrustの1.39.0からDeprecatedとなっており、MaybeUninitに変わっていました。どうやって使うのかを調べたメモです。

内容はドキュメントに書いてあるものを少し噛み砕いているだけです。ソースコードgkuga/rust-examples/maybeuninitに置きました。

MaybeUninit

Rustでは初期化していない参照にアクセスすることはできません。コンパイラがプログラムのメモリ安全性を保証するためです。例えば変数を初期化する関数make_vec()を作ります。そしてmain()で作った変数vを初期化します。

src/main.rs

unsafe fn make_vec(out: *mut Vec<i32>) {
    out.write(vec![1, 2, 3]);
}

fn main() {
    let mut v: Vec<i32>;
    unsafe {
        make_vec(&mut v);
    }
    println!("{:?}", v);
}

このようなコードはエラーになります。

$ cargo run
   Compiling maybeuninit v0.1.0 (/home/vagrant/dev/src/github.com/gkuga/rust-examples/maybeuninit)
error[E0381]: borrow of possibly-uninitialized variable: `v`
 --> src/main.rs:8:18
  |
8 |         make_vec(&mut v);
  |                  ^^^^^^ use of possibly-uninitialized `v`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0381`.
error: could not compile `maybeuninit`.

To learn more, run the command again with --verbose.

初期化していない変数を渡しているからです。だからといって適当な値で初期化してから初期化用の関数に渡すのも可読性が下がりそうです。そういう時にはMaybeUninitを使って変数が初期化されていないかもしれないということを明確にします。

src/main.rs

use std::mem::MaybeUninit;

unsafe fn make_vec(out: *mut Vec<i32>) {
    out.write(vec![1, 2, 3]);
}

fn main() {
    let mut v = MaybeUninit::uninit();
    unsafe {
        make_vec(v.as_mut_ptr());
    }
    let v = unsafe { v.assume_init() };
    println!("{:?}", v);
}

実行すると以下のようになりました。

$ cargo run
   Compiling maybeuninit v0.1.0 (/home/vagrant/dev/src/github.com/gkuga/rust-examples/maybeuninit)
    Finished dev [unoptimized + debuginfo] target(s) in 0.32s
     Running `target/debug/maybeuninit`
[1, 2, 3]

vに代入する値をMaybeUninit::uninit()としています。この関数の戻り値はMaybeUninit<T>です。初期化されていないということがわかりやすくなります。make_vec()を呼び出す時はas_mut_ptr()Tの参照を取得します。最後にassume_init()で初期化された値を取り出します。このメソッドで取り出された値は他の変数と同様にdrop()が呼ばれるのがメリットなようです。

おわりに

初期化されていない参照を扱いたい時にMaybeUninitのことを思い出そうと思いました。

また、assume_init()が何をやっているのかよくわかっていないので、次の機会に追ってみようと思います。

    pub unsafe fn assume_init(self) -> T {
        intrinsics::panic_if_uninhabited::<T>();
        ManuallyDrop::into_inner(self.value)
    }