Skip to content

Document that casting &mut T to &mut MaybeUninit<T> is not safe #66699

Closed
@Shnatsel

Description

@Shnatsel
Member

As discussed on Reddit, it turns out that casting &mut T to &mut MaybeUninit<T> is not a safe operation, since it allows UB in safe code.

Playground example demonstrating the UB: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=93dd41cf851bfc0de2233e0a83b4b778

It is probably a good idea to explicitly point that out in MaybeUninit docs.

Activity

jannickj

jannickj commented on Nov 24, 2019

@jannickj

In the example you are already required to wrap the code in unsafe so I am curious why it's necessary to state explicitly for MaybeUninit<T>? transmute is unsafe for most types not just MaybeUninit.

added
C-enhancementCategory: An issue proposing an enhancement or a PR with one.
A-docsArea: Documentation for any part of the project, including the compiler, standard library, and tools
on Nov 24, 2019
Kixunil

Kixunil commented on Nov 24, 2019

@Kixunil
Contributor

@jannickj if you try to write a safe wrapper around MaybeUninit<T>, then the fact that T can be soundly transmuted into MaybeUninit<T> does not mean that you can transmute &mut T into &mut MaybeUninit<T> and expose the resulting reference to safe code. That's something easily overlooked.

FWIW, there's at least one crate in the wild that is unsound because of this mistake. Hey @danielhenrymantilla you probably want to fix it. I wrote you an e-mail regarding other stuff, no reply yet.

danielhenrymantilla

danielhenrymantilla commented on Nov 24, 2019

@danielhenrymantilla
Contributor

@Kixunil yes, thanks for the e-mail, I've been quite busy lately so I have not had much time to address this (or answer the e-mail 😉), but I'll try to have it fixed by tomorrow, with the previous versions being yanked (and the repo being published).


So, to summarize the issue, it turns out that &'a mut MaybeUninit<T> is far stronger than an OutRef<'a, T>, since it allows writing garbage. If we had an OutRef<'a, T> abstraction that only allowed to write a T into the pointee, then downgrading a &'a mut T to an OutRef<'a, T> should be sound, right? And I expect &'a mut MaybeUninit<T> -> OutRef<'a, T> to be sound as well.

image

Which, when mem::needs_drop::<T>().not(), means that OutRef<'_, T> has no downsides to write dava w.r.t. &'_ mut T


Thanks @Shnatsel and @Kixunil for reporting this!

Kixunil

Kixunil commented on Nov 25, 2019

@Kixunil
Contributor

Yes, I believe that's sound. Since I didn't get the answer to the e-mail I started creating a new crate from scratch that's addressing my needs. :) It's not published yet, but has bunch of other cool stuff. (Solves the same problem for slices, has tons of helper methods, iterator, safe cursor...) We can still cooperate if you want.

Kixunil

Kixunil commented on Nov 25, 2019

@Kixunil
Contributor

FYI, here's the crate: https://crates.io/crates/possibly_uninit I hope it will help prevent these kinds of bugs.

Kixunil

Kixunil commented on Nov 27, 2019

@Kixunil
Contributor

Interestingly BytesMut from bytes crate is also unsound. I fear that stabilizing MaybeUninit without having safe (or at least sound) abstractions for most common stuff was a major footgun.

Kixunil

Kixunil commented on Nov 27, 2019

@Kixunil
Contributor

How hard would it be to add some basic abstractions into core to avoid others screwing up? Does it require RFC? Maybe just something for slices and mutable references, not stabilizing too many methods yet.

RalfJung

RalfJung commented on Nov 28, 2019

@RalfJung
Member

To me this looks like someone mistakenly assumed &mut T to be covariant in T: in some sense, MaybeUninit<T> is a supertype of T, but that doesn't mean you can convert them below an &mut.

But, sure, if someone has a good suggestions for where in the MaybeUninit docs to put this, that seems fine.

RalfJung

RalfJung commented on Nov 28, 2019

@RalfJung
Member

@Kixunil Having a user-defined OutRef type seems like a reasonable approach. However, it looks like you are still using "uninit" terminology in your crate. I am particularly worried about this type. An "out ref" and a "reference to MybeUninit" are two fundamentally different concepts as this issue shows, so please be careful in picking terminology that conveys that difference! Your current names, I am afraid, will only make things worse. A "MaybeUninitSlice" should be just &mut [MaybeUninit<T>], by using the same term for the totally different concept of an OutSlice you might cause confusion.

How hard would it be to add some basic abstractions into core to avoid others screwing up? Does it require RFC? Maybe just something for slices and mutable references, not stabilizing too many methods yet.

See #63569. However, what you are asking for is not "something for slices and mutable references to MaybeUninit"; what you are asking for is something entirely distinct, namely write-only references for initialization purposes.

Kixunil

Kixunil commented on Nov 28, 2019

@Kixunil
Contributor

Thanks for recommendation, good point, I will change it (that's why I called it "preview" :)). Is the name OutRef something standard and widely understood or just a suggestion? I didn't hear such thing before.

RalfJung

RalfJung commented on Nov 28, 2019

@RalfJung
Member

&out as complement to &mut has come up in discussions again and again over the years. This is related to in-place initialization / "placement new", which has a looooooong history in Rust. Swift has "in" and "inout" references; "out" seems like a natural name for this pattern.

However, note that there is AFAIK no way to implement safe &out as a library. The issue is that you need to somehow force a write to happen. (Well there might be tricks you can play with generative lifetimes but it's going to not be very ergonomic.) The BufMut issue is special because the safety of the problematic API relies on things already being initialized to begin with. So, I think there are very few cases where OutSlice actually makes an API safe. That type is not truly an out-reference as it doesn't enforce initialization. Hm.

Kixunil

Kixunil commented on Nov 28, 2019

@Kixunil
Contributor

OK, I will rename it to OutRef and OutSlice.

Yes, the lack of enforcing is something I noticed. It's the primary reason I added Cursor for slices and I was thinking about doing something similar for Sized types (call it Slot? IDK).

It is totally true that "OutSlice" is not very useful in itself as the code interacting with it will be mostly unsafe (it can be nicely seen in Cursor impls). However, I do think that preventing footguns in unsafe code still has a value. At least documentation value that could prevent mistakes like these.

6 remaining items

danielhenrymantilla

danielhenrymantilla commented on Oct 15, 2020

@danielhenrymantilla
Contributor

By the way, this issue is related to another interesting one: for &T -> &MaybeUninit<T> to be sound, we need to express that UnsafeCell and MaybeUninit do not commute! More info in the docs of ::uninit: https://docs.rs/uninit/0.4.0/uninit/out_ref/struct.Out.html#interior-mutability

tesuji

tesuji commented on Oct 17, 2020

@tesuji
Contributor

When I first saw the title, I was expecting that casting is &mut T as &mut MaybeUninit<T>.
Isn't it a transmute and not a cast ? I would like to change the issue title to reflect that.

Kixunil

Kixunil commented on Oct 17, 2020

@Kixunil
Contributor

Is &mut *(foo as *mut _ as *mut MaybeUninit<T>) considered transmute?
Anyway, the title should (also) s/safe/sound/. The fact it's unsafe is already obvious.

thomcc

thomcc commented on Oct 30, 2020

@thomcc
Member

It's not always unsound, just sometimes. The usual way of describing this is unsafe.

ojeda

ojeda commented on Jan 19, 2021

@ojeda
Contributor

Something cannot be sometimes unsound -- it is either unsound or not. Unsafe does not imply unsound or sound.

thomcc

thomcc commented on Jan 19, 2021

@thomcc
Member

It's unsound if you write MaybeUninit::uninit to it, or expose it to safe code you don't control (that can do this trivially). It's sound otherwise.

ojeda

ojeda commented on Jan 19, 2021

@ojeda
Contributor

That means it is unsound. It doesn't matter that sometimes there may not be code triggering the UB.

Kixunil

Kixunil commented on Jan 19, 2021

@Kixunil
Contributor

So the title should be "Document that casting &mut T to &mut MaybeUninit and exposing it to safe code is not sound"

added a commit that references this issue on Dec 22, 2024

Rollup merge of rust-lang#134583 - Enselic:maybe-uninit-transmute, r=…

6ade237
added a commit that references this issue on Dec 23, 2024
5e976fb
added a commit that references this issue on Mar 11, 2025

Rollup merge of rust-lang#134583 - Enselic:maybe-uninit-transmute, r=…

3f039d4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-docsArea: Documentation for any part of the project, including the compiler, standard library, and toolsC-enhancementCategory: An issue proposing an enhancement or a PR with one.T-langRelevant to the language team

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Participants

      @Shnatsel@RalfJung@thomcc@ojeda@Kixunil

      Issue actions

        Document that casting &mut T to &mut MaybeUninit<T> is not safe · Issue #66699 · rust-lang/rust