bluss / arrayvec

A vector with a fixed capacity. (Rust)
https://docs.rs/arrayvec/
Apache License 2.0
733 stars 127 forks source link

Add "placement new"-like constructor API #277

Open aleksanderkrauze opened 2 hours ago

aleksanderkrauze commented 2 hours ago

Feature description

Add new constructors to ArrayVec and ArrayString, that instead of returning initialized Self, write to user-provided out-pointer.

Rationale

Consider following case. I want to use a heap allocated Vec-like structure, but with const-known maximum capacity. I would like to use Box<ArrayVec<T, N>> as a backing storage. However creating such type is problematic. For sufficiently big N expression Box::new(ArrayVec::new()) may overflow stack. Box (and other types in standard library) have currently unstable (but stable in current beta, which will hit stable in 3 days) API new_uninit that helps to solve this exact case. However arrayvec does not have any API that would allow constructing its types in-place, which makes it impossible to safely use aforementioned std APIs. By adding this kind of constructors, ArrayVec becomes usable in described scenario (and others that require in-place initialization).

Drawbacks

Other possibilities

One possibility is to just do nothing. Users who wish to use placement-new-like constructors can just re-implement ArrayVec manually.

There is also a dark and unsafe way. Since ArrayVec has #[repr(C)], one can create their own mirror type, initialize it, and then std::mem::transmute it into arrayvec::ArrayVec. I think it requires no further explanation why this should not be preferred by anyone. :)

Third possibility would be to make ArrayVec's fields public, which would allow users to instantiate it how they wish. I do not want to endorse this, just mention it for the sake of completeness.

Possible implementation

Here is a possible implementation:

impl<T, const CAP: usize> ArrayVec<T, CAP> {
    pub fn new_in(dest: &mut MaybeUninit<Self>) {
        let dest = dest.as_mut_ptr();
        unsafe {
            let len_ptr = core::ptr::addr_of_mut!((*dest).len);
            len_ptr.write(0);
        }
    }
}

It could be used like this:

let mut stack: Box<ArrayVec<i32, N>> = {
    let mut uninit_stack = Box::new_uninit();

    ArrayVec::new_in(&mut uninit_stack);

    unsafe { Box::<MaybeUninit<_>>::assume_init(uninit_stack) }
};

Open questions

aleksanderkrauze commented 2 hours ago

If this feature request is accepted, I can provide PR implementing it.