Why does MaybeUninit not implement Default unconditionally?

It surprises me, that this type doesn't implement the Default trait. Is there a good reason for that? I'd suggest adding it, if there isn't. Would it be OK to just make a pull request to add this or does this need an RFC?

Just a guess for why it might have no Default implementation for a reason: there might be the possibility that people would assume that MaybeUninit’s default value is MaybeUninit::new(Default::default()). And getting/guessing this wrong for unsafe code can lead to UB.

This is just my personal thoughts. We should also look for previous discussions on this topic.

Since (AFAIK) trait implementations cannot be added behind a feature flag, I don’t believe that merging a PR directly would work. RFC might be the right way, but someone more familiar with the requirements for stabilizing changes in the standard library should comment on this.

4 Likes

Note that Option::<T>::default() is None for all T. I think it's irresponsible to assume users writing unsafe code haven't carefully read the docs of the types in question.

This has already been denied

7 Likes

It's not "irresponsible", it's realistic and cautious. With an API so common as Default, this assumption is entirely plausible.

Furthermore, Option doesn't cause insta-UB when its Default impl is accessed, as there's nothing inherently unsafe about None (unlike the uninitialized state of MaybeUninit).

1 Like

Unless you misread what I wrote, the entire point of unsafe markers is to indicate that some invariant the compiler can't enforce needs to be upheld. If users are performing unsafe operations without reading documentation, that's on them, not on the library. There is no room for "hmm, I guess this is probably how this works" when writing unsafe code.

There is nothing inherently unsafe about MaybeUninit<T>::default() returning uninitialized data, as pointed out in the linked issue; the reason this feature got a lukewarm response last it was proposed is because its utility is small vs. the documentation burden.

Sure, but this obligation is not an argument against providing safer-to-use and harder-to-abuse APIs.

3 Likes

Here's a question: what benefit would being able to use MaybeUninit in an API taking T: Default be?

I could see it for maybe a safe Vec resize API that default-initializes new elements, but we don't even have that API to use with a Vec<MaybeUninit<_>>. We do have (unstable) Vec::spare_capacity_mut, which is better, because now you actually can use how the Vec tracks which slots you've initialized.

Maybe it's useful for #[derive(Default)]. But this could also be equivalently served by explicit default values, since MaybeUninit::uninit is const.

What triggered the question/request was this small piece of code:

You were on spot with Vec::resize_with.

Even unsafe APIs require a lot of thought, they need to be easy to use correctly and be designed defensively. Personally I may have an exaggerated preference for debug assertions, but it's an example of something that can help to be defensive.