-
Notifications
You must be signed in to change notification settings - Fork 19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ACP: ForwardInit<'a, T>
to complement MaybeUninit<T>
#438
Comments
ForwardInit<T>
to complement MaybeUninit<T>
ForwardInit<'a, T>
to complement MaybeUninit<T>
Before I criticize this, I want to say that I think this is a good step forward, and that I have thought about this api (under a different name) some time ago. So maybe I can lend some insight. This isn't as useful as it seems in public apis. For example: pub fn foo(init: ForwardInit<T>) { ... } The caller has no guarantees that Of course this doesn't affect private apis, but then it's only marginally better than just using So I think for // actual newtype wrapper omitted here
enum ForwardInit<'a, T> {
Mu(&'a mut MaubeUninit<T>),
Ref(&'a mut T)
} Where it automatically switches from This could be combined with an optimization to take advantage of pointer alignment niches so it's still just one ptr large, but that's not necessary. |
So, maybe you were reading the version that just had versions of IMHO, while an enum is okay, it doesn't really offer the same ergonomics, and additionally, it won't work for slices either. |
Yes, I think I did. However, a signature like
I agree that the ergonomics may be more difficult to achieve, but I think it should be possible to implement all the methods you did. Can you elaborate on why an enum wouldn't work for slices? |
|
The main use case for this API is for read-like APIs which return a variable length slice into a user-provided buffer. This would allow such methods to accept both Could you give more examples of how this API could be used in practice? How does it interact with |
To clarify, I don't think this would actually affect the API for However, the expectation would be that simpler APIs similar to read could look like the following: fn read_buf<'b>(&mut self, buf: ForwardInit<'b, [u8]>) -> &'b [u8]; (note that the choice to return This API is a bit different from Maybe a similar analogy to an API that might look similar to this is And also to further clarify: the main benefit of an API like this is that you can write one function that works with |
Proposal
Problem statement
Right now, there's no good way to generalise "write-only" memory with
MaybeUninit
due to one caveat:&mut MaybeUninit<T>
gives you the ability to de-initialize memory, which makes it unsound to create a&mut MaybeUninit<T>
from a&mut T
.Motivating examples or use cases
BorrowedBuf
is a type in the standard library which would benefit from this pattern internally, although in general, the use case is wherever you wish to write to a value but not read from it.For cases where you're only writing, it's useful to be able to allow both uninitialized memory and already-initialized memory, although right now, there's no way to reuse the same function to perform both these operations without using unsafe code.
You always run into one of the following issues:
*mut T
) instead of a reference, which requires unsafe code inside the function doing the writing.&mut T
) to maybe-uninit memory (&mut MaybeUninit<T>
) and make the unsafe assertion on both sides of the function that the memory is not de-initialized.(note: all of the above also apply to already-init slices (
&mut [T]
) and maybe-uninit slices (&mut [MaybeUninit<T>]
) but for brevity, the above notation is used)Solution sketch
A new type,
ForwardInit<'a, T>
, is added as a conceptual wrapper over&'a mut MaybeUninit<T>
. It adds the safety invariant that even if the memory starts uninitialized, it cannot be de-initialized; the initialization is in the forward direction only. Similar toPin
, we ensure the lifetime is "trapped" inside the type to disallow directly mutating the memory inside.I have some working code up on another repo but I'll provide a basic overview of what I think is most relevant for now before additional API nitpicking.
First,
MaybeUninit<T>
currently does not allow unsized types. However, since we're wrapping&mut MaybeUnint<T>
anyway, we can get away with just storing a pointer inside the actual struct:This will allow
ForwardInit<'a, [T]>
and similar types, which are arguably the ones people are most commonly going to be using.Its constructors will be from other mutable references:
Since we have to ensure that any references are trapped inside the type, we can't offer an
Index
implementation for slices, but we can still allow indexing them with dedicated methods:(Note:
SliceIndex
will have to be modified to accomodate this type, but that should be okay. In my sample code, I had to rely onClone
andSliceIndex<[MaybeUninit<T>]>
to ensure soundness.)Additionally, we can offer an iterator for slices as well:
Initially, to allow reborrowing, I decided to make the
write
andassume_init
methods take&mut self
instead ofself
. This messed with APIs however, and I decided to add aby_ref
method similar to iterators to reborrow:And here are some sample slice methods:
Like the
MaybeUninit
API, this would likely be rolled out in stages depending on what feels most useful to the community, and what APIs are desired. The biggest proposal here is the idea of aForwardInit<'a, T>
type itself, and we can change how we fill in the blanks later.Alternatives
As of now, the
BorrowedBuf
API is probably the closest to something that might offer something similar to this. However, this does show that there is some kind of desire for this type of API, even in the standard library.Additionally, one very reasonable alternative is… I implemented this as an external crate, so, keep it that way. This has some of the downsides of being unusable in the standard library and potentially fragmenting the ecosystem with different implementations, but the upsides of not requiring any stdlib maintenance.
Links and related work
Sample implementation: https://codeberg.org/clarfonthey/forward-init/src/branch/main/src/lib.rs
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:
The text was updated successfully, but these errors were encountered: