Why does the example in the MaybeUninit documentation use ptr::write instead of MaybeUninit::new?

Hello!

I'm currently playing around with MaybeUninit a little bit and I was reading the example code here: MaybeUninit in std::mem - Rust

In this code, individual array elements get initialized in a loop by writing to their pointer location like this:

for elem in &mut data[..] {
    unsafe { ptr::write(elem.as_mut_ptr(), vec![42]); }
}

Now I wonder - is this different from using MaybeUninit::new ?

for i in 0..1000 {
    data[i] = MaybeUninit::new(vec![42]);
}

Thanks!

1 Like

No it is the same, but that's only because MaybeUninit ignores the destructor of whatever is inside it. Normally the expression data[i] = something will first drop whatever is stored at data[i], which is typically a bad thing to do if the memory is uninitialized.

The ptr::write makes it obvious that destructors are not run .

2 Likes

It is overly pedantic example, as you are supposed to use ptr::write to correctly write into uninitialized memory, it is perfectly reasonable to do as you suggested too

Well there is difference is that moving into array without ptr::write would drop element, but in case of MaybeUninit inside it would do nothing(and most likely compiler should optimize it away)

Just a nitpick: let's avoid using indexing for the second version, since that it is definitely a semantic change compared to the original iterator of &muts:

for elem in &mut data[..] {
    // unsafe { ptr::write(elem.as_mut_ptr(), vec![42]); }
    *elem = MaybeUninit::new(vec![42]);
}
  • Addendum

    Do note that there is an unstable method on MaybeUninit<T> to be able to instead write:

    elem.write(vec![42])
    

    which is more idiomatic and has also the advantage of lending the non-unsafe equivalent &mut T of calling MaybeUninit::get_mut() right afterwards :slightly_smiling_face:

and this has the main advantage of not requiring unsafe, so it is indeed a nice change you have spotted there :wink:


The version with ptr::write is aimed at using FFI: imagine having a C library offering the following extern "C" function:

use ::libc::{c_int, c_void, size_t};

type Thing = *mut c_void;

const STATUS_OK: c_int = 0;

extern "C" {
    fn create (out: *mut Thing, arg: size_t) -> c_int; // returns a status code
}

then you could initialize an array of Things with:

const N: usize = 100;

let mut array: [MaybeUninit<Thing>; N] = unsafe {
    MaybeUninit::uninit().assume_init() // == mem::uninitialized()
};

for (arg, elem) in array.iter_mut().enumerate() {
    let status = unsafe {
        create(elem.as_mut_ptr(), arg)
    };
    if status != STATUS_OK { ... }
}
2 Likes

Thank you, this is definitely helpful!

Reading & thinking about your answers led me to another, related question. What if I want to replace a single, specific element in my [MaybeUninit<Thing>; N].
So for example, would it be a good idea to turn this (code adapted from Redirecting...):

unsafe {
    ptr::write(self.data.as_mut_ptr().offset(index), elem);
}

into the following safe code?

self.data[index] = MaybeUninit::new(elem);

You should remember that depending on context, you might need to drop element manually before over-writing it after initial initialization.

I would actually prefer to use MaybeUninit<[Thing; N]> because it forces you to access each value through unsafe.

But answering your question, yes it is safe.
Just remember that inside values are ManuallyDrop which means they are not dropped automatically

1 Like

Are you sure using a MaybeUninit<[Thing; N]> that way is defined? I'm pretty sure it's undefined to assign each field separately, so the same should apply to arrays, no?

You'll have to access each element through raw pointer, and then use ptr::write, ptr::read and ptr::drop_in_place, and etc. It enforces unsafe semantics comparing to just plain array with MaybeUninit elements

Yes, any equivalent pattern that leads to not using unsafe is to be preferred. Do note, however, that the code using pointer arithmetic performs no bound check although the non-unsafe version will (unless optimized away by the compiler thanks to contextual information).


The issue with Rust structs is that the offset to a particular field of the struct is not known, leading to a lack of a sound offset_of macro / compiler built-in, and that's what forbids using this pattern to initialise each field of a struct separately.

If we had such a safe construct, we could do:

struct Foo {
    field1: Field1,
    field2: Field2,
}

// convoluted way to do Foo { field1: Field1::new(), field2: Field2::new() }
let foo = unsafe {
    let mut foo = MaybeUninit::<Foo>::uninit();
    let base_ptr = foo.as_mut_ptr() as *mut u8;
    ptr::write(
        base_ptr.add(offset_of!(Foo => field1)) as *mut Field1,
        Field1::new(),
    );
    ptr::write(
        base_ptr.add(offset_of!(Foo => field2)) as *mut Field2,
        Field2::new(),
    );
    foo.assume_init()
};

But in the case of an array, the offset from one of its values to the base of the array is known, so there is no issue.

3 Likes

Not necessary true. Safe equivalent still leaks memory and makes you way too relaxed about it.
Comparing to unsafe which actually makes you think about what you're doing

I believe it will be reasonably safe to use field accessors as it is through pointer though.
So it is not really issue

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.