rust-lang / libs-team

The home of the library team
Apache License 2.0
110 stars 18 forks source link

ACP: Allow generalising between `&mut T` and `&mut MaybeUninit<T>` #329

Open clarfonthey opened 5 months ago

clarfonthey commented 5 months ago

Proposal

Problem statement

In general, code that initializes a buffer doesn't need to care whether that buffer is initialized, however, this leads to an issue. Since &mut MaybeUninit<T> allows writing any maybe-uninit value to it, you can't cast &mut T to &mut MaybeUninit<T>, since it would allow you to write an uninit value to a definitely-init location. Thus, in order to have a mutable reference which is maybe-uninit, but cannot be de-initialized, you need a separate type.

Motivating examples or use cases

Although the current std::io::Read cannot be changed, other custom Read types could borrow this approach, allowing something like:

fn read(&mut self, buf: &mut [MaybeUninit<u8>]) -> Result<&[u8], Error>;

Although this currently would require passing in an initially-uninit buffer, and you can't pass an existing &mut [u8] here.

Similarly, any such code which expects to initialize a buffer, but which will not read from the buffer beforehand, could benefit from a separate type which allows initialization, but not de-initialization.

Solution sketch

Two solutions will be presented, basically depending on how we feel about MaybeUninit<T> where T: ?Sized.

Unsized MaybeUninit version

We could add a type Initializable<'a, T>, which internally holds an &mut MaybeUninit<T> but does not grant actual access to the internal pointer to ensure that it is never de-initialized. It will expose the by-reference methods on the MaybeUninit<T> directly, including ones which initialize the MaybeUninit<T> as an initialized, mutable reference, but will not allow writing directly to the reference with safe code.

No-unsized MaybeUninit version.

We could add a type WriteOnly<T>, which wraps &mut MaybeUninit<T>, &mut [MaybeUninit<T>], &mut [MaybeUninit<T>; N], etc. (e.g. WriteOnly<&mut MaybeUninit<T>>) to provide the same functionality of above. This makes it similar to Pin in the sense that you cannot directly access the underlying reference with safe code.

Purpose

Essentially, offering one of these two types would let you initialize them with either a pre-existing, initialized buffer, or a MaybeUninit buffer, while remaining sound.

Offering these in the standard library would allow eventually using them in standard library APIs, for example, like reading to uninitialized buffers. Since the current shape of the MaybeUninit APIs is still undecided for arrays and slices, this would help solidify those as well and offer a solid way for users to write code that initializes maybe-uninitialized buffers.

Alternatives

The biggest alternative is offering this via a crate, which I had been thinking of writing for a while, but never got to. The reason why I'm proposing an ACP right now is because after looking at my existing code, it's pretty trivial to implement, but the more important decision here is where this code should ultimately lie. If this ACP isn't accepted, the alternative would definitely live on in an external crate.

Links and related work

N/A

What happens now?

This issue contains an API change proposal (or ACP) and is part of the libs-api team feature lifecycle. Once this issue is filed, the libs-api team will review open proposals as capability becomes available. Current response times do not have a clear estimate, but may be up to several months.

Possible responses

The libs team may respond in various different ways. First, the team will consider the problem (this doesn't require any concrete solution or alternatives to have been proposed):

Second, if there's a concrete solution: