はじめに
MaybeUninitのassume_initメソッドについて調べたのでまとめたの内容と少し被っています。Rust 1.42でStabilized APIsになったManuallyDrop::take
について調べたのでまとめました。
ManuallyDrop構造体
ManuallyDrop構造体はコンパイラに自動で値の破棄をさせたくない場合に使います。定義では以下のようになっています。
pub struct ManuallyDrop<T: ?Sized> { value: T, }
valueとしてT型の値を単純に持っているだけです。構造体を作成する時はnew()
を呼び出します。
pub const fn new(value: T) -> ManuallyDrop<T> { ManuallyDrop { value } }
値を取り出したい時はManuallyDrop::into_inner
を呼び出します。すると中の値をMoveして取り出せます。その値を破棄する場合はlet _ =
で束縛しないで捨てます。ManuallyDrop::drop
でも破棄できますが、into_innerと違いunsafeであり、破棄後に中の値にアクセスしないように自分で管理する必要があります。
ManuallyDrop::take
ManuallyDrop::takeはunsafeな関数でManuallyDropの持つvalueのコピーを返します。
定義は以下です。ptr::read
にslot.value
の参照を渡しています。
pub unsafe fn take(slot: &mut ManuallyDrop<T>) -> T { ptr::read(&slot.value) }
ptr::read
ptr::readは渡した値のコピーを返します。
定義は以下です。渡されたslot.value
の参照をイミュータブルなRaw Pointerである*const T型の変数src
で受け取ります。適当な領域であるtmpを用意してcopy_nonoverlappingでsrc
(slot.value
の中身)をコピーし、返します。
pub unsafe fn read<T>(src: *const T) -> T { let mut tmp = MaybeUninit::<T>::uninit(); copy_nonoverlapping(src, tmp.as_mut_ptr(), 1); tmp.assume_init() }
copy_nonoverlappingは組み込み関数で以下のような型です。
pub unsafe fn copy_nonoverlapping<T>(src: *const T, dst: *mut T, count: usize)
src
からdst
へcount * size_of::<T>()
バイト分をコピーします。ptr::readの定義でcountを1
にしているのはここではslot.valuel
の分ということです*1。
MaybeUninit::as_mut_ptはMaybeUninit共有型に格納されている中の値のミュータブルなアドレスを返します。
MaybeUninitを初期化したら最後にtmp.assume_init()
で初期化された値を返しています。
ManuallyDrop::takeで気をつけたいこと
特に自分が直面したわけではないですが、こういうことも起こり得るかなということを書きます。
#![allow(unused)] use std::mem::ManuallyDrop; fn main() { let mut orig_val = ManuallyDrop::new("something".to_string()); println!("{:?}", orig_val); unsafe { let taked_val = ManuallyDrop::take(&mut orig_val); } println!("{:?}", orig_val); }
上記のプログラムでlet taked_val = ManuallyDrop::take(orig_val)
した後にtaked_val
の値がdropされると中で保持していたStringも破棄されます。なのでorig_val
にアクセスすると不正な値を参照してしまうかもしれません。
掲載したプログラムを動かすと以下のようになりました。
$ cargo run Finished dev [unoptimized + debuginfo] target(s) in 0.01s Running `target/debug/manually_drop` ManuallyDrop { value: "something" } ManuallyDrop { value: "\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{10}" }
おわりに
Stable APIになったタイミングで実装が少し変わっていました。
pub unsafe fn take(slot: &mut ManuallyDrop<T>) -> T { - ManuallyDrop::into_inner(ptr::read(slot)) + ptr::read(&slot.value) }
ManuallyDropをコピーして中の値を取り出していたのをManuallyDropが保持するvalue
をコピーするようにしたというリファクタリングですね*2。