-
Notifications
You must be signed in to change notification settings - Fork 225
Description
The problem
The current implementation of uninit_vector() causes undefined behavior, and is especially dangerous to use with types that manage resources (and thus implement Drop) such as Box or Arc.
This is due to how the API forces you to initialize the vector:
let v: Vec<Box<T>> = uninit_vector(10);
for idx in 0..10 {
// Rust thinks that `v` is initialized, and so drops the previous value stored at `v[idx]`
// which in our case is uninitialized, and hence causes undefined behavior.
v[idx] = Box::new(T);
}You can test this for yourself with the following snippet
pub struct MyStruct(u32);
impl Drop for MyStruct {
fn drop(&mut self) {
std::println!("MyStruct is being dropped, with value {}", self.0);
}
}
// Output:
// before assignment
// MyStruct is being dropped, with value <undefined; depends on your system/environment>
// after assignment
// MyStruct is being dropped, with value 42
fn main() {
let mut v: Vec<MyStruct> = unsafe { uninit_vector(1) };
std::println!("before assignment");
v[0] = MyStruct(42);
std::println!("after assignment");
}The first call the MyStruct::drop() is due to the assignment dropping the former (uninitialized) value in the vector; the second call happens at the end of main() when v is dropped (and all values in the vector).
We probably never noticed anything bad happening before, since the main use of uninit_vector is with Field types that don't implement Drop. However, using it with any type still causes undefined behavior and we should move away from it.
The solution
The solution is to use MaybeUninit which was designed for this use case. So we should mark uninit_vector as deprecated and provide a new
pub unsafe fn uninit_vector2<T>(length: usize) -> Vec<core::mem::MaybeUninit<T>> {
let mut vector = Vec::with_capacity(length);
vector.set_len(length);
vector
}Then callers use MaybeUninit::write() to set the value (which very importantly does not call drop() on the old value as the assignment operator does). The previous broken example now becomes
// Output:
// before assignment
// after assignment
// MyStruct is being dropped, with value 42
fn main() {
let v: Vec<MyStruct> = {
let mut v: Vec<MaybeUninit<MyStruct>> = unsafe { uninit_vector2(1) };
std::println!("before assignment");
v[0].write(MyStruct(42));
std::println!("after assignment");
// SAFETY: We have initialized all values in the vector, so it is safe to transmute.
unsafe { core::mem::transmute(v) }
};
}Note that using transmute() is the documented way of doing this.