For a long time, there's not really been a sane way to construct custom dynamically-sized types (DSTs). They can't be constructed on the stack, so they must be constructed on the heap, but none of the standard library's heap-allocated containers can handle custom DSTs. Box
, Rc
and Arc
can be constructed by moving a Sized
type into them, or by constructing a slice of MaybeUninit
, but nothing beyond that. Box::from_raw
can take a pointer to a memory allocation, which you can in theory use to construct a DST before giving Box
ownership of the memory, but Rc
and Arc
have no such option, as the pointer needed for their from_raw
constructors can only be obtained from a previously constructed container.
I've thought about possible ways out of this tangle, but I'm not sure which one is the most promising option that I should pursue with a followup RFC.
1. Unsized unions
The main reason that the above containers can't be constructed with MaybeUninit
for arbitrary types is that it's a union, and unions must be sized. You can have a [MaybeUninit<T>]
but not a MaybeUninit<[T]>
. I found some discussions about allowing (some subset of) unsized unions:
- Zulip: t-opsem > Unions with unsized fields
- Internals: Pre-Pre-RFC: Dynamically sized enum types
- Internals: DST unions
The most conservative would be to allow a union to contain exactly one unsized variant, just like structs. It could even be required to be the last, though for a union that seems superfluous. Because this would be a change to the core language itself, it probably has the most soundness ramifications, and therefore seems like the most likely to run into roadblocks and stall. But it also seems like this is the option everyone ultimately "wants" to happen in the long term.
2. UniqueRcUninit
UniqueRc
is a relatively new and still unfinished API for constructing an Rc
. It seems to have been added primarily for creating cyclic reference chains, but it could also be used to construct values in-place. In Discord, @kpreid pointed me towards a private helper type for UniqueRc
, called UniqueRcUninit
, that was merged only a few days ago. Its purpose is to help with allocating memory for UniqueRc
, and then providing some way to initialise it. If this were made public, and we added BoxUninit
and UniqueArcUninit
, then this could work around the limitations of unions. But as a private type it may not be suitable for a public API without some changes.
3. New allocation-only constructors
If neither of the above options are feasible, then there is a fallback option, which is to add a completely new API. A possible API was suggested in Discord, with some adjustments by me:
impl<T> Rc<T>
where
T: ?Sized + Pointee
{
pub fn alloc(metadata: <T as Pointee>::Metadata) -> *mut T;
pub unsafe fn dealloc(ptr: *mut T);
}
These functions would also be added to Box
and Arc
. Essentially, these functions would do the same with a manual raw pointer that UniqueRcUninit
does as a custom type:
- The
alloc
constructor would allocate memory, and then return a raw pointer to theT
within the allocation. This would be the same pointer that's returned by theinto_raw
method of these three types, but can be obtained without first obtaining aBox
/Rc
/Arc
with an initialisedT
. The user would then use this pointer to initialise theT
, and then callfrom_raw
to create the final container. - The
dealloc
function is needed in case construction of theT
fails in some way, and the memory needs to be freed without ever initialising the memory and creating the container type.
Do you think any of these ideas is worth investigating further and turning into an RFC?