Skip to content

General purpose concat_slices function #7

@tigerros

Description

@tigerros

I looked at the internals and was wondering if concat_slices can't be a const function instead. I took inspiration from the "internal" concat function:

enum ConcatError {
    LenGreaterThanSum,
    LenLesserThanSum,
}

/// Soundly concatenates all items in the given slices.
/// References items, because we don't assume [`T`] is [`Copy`].
///
/// # Errors
/// Errors if the length of all items does not match [`LEN`].
const fn concat_slices<'a, const LEN: usize, T: 'a>(
    slices: &'a [&'a [T]],
) -> Result<[&'a T; LEN], ConcatError> {
    let mut out: [MaybeUninit<&'a T>; LEN] = [MaybeUninit::uninit(); LEN];
    let mut out_i = 0;
    let mut slice_i = 0;

    while slice_i < slices.len() {
        let slice = slices[slice_i];
        let mut item_i = 0;

        while item_i < slice.len() {
            // We reached LEN but there's another item to process
            if out_i == LEN {
                return Err(ConcatError::LenLesserThanSum);
            }

            // For older Rust versions change to:
            // out[out_i] = MaybeUninit::new(&slice[item_i]);
            out[out_i].write(&slice[item_i]);
            out_i += 1;
            item_i += 1;
        }

        slice_i += 1;
    }

    // Index should normally not be equal to the length so you might think that
    // <= would be applicable here, but remember that after the last item (index) is processed,
    // we still increment out_i.
    if out_i < LEN {
        return Err(ConcatError::LenGreaterThanSum);
    }

    // SAFETY:
    // MaybeUninit<T> has the same size, alignment, and ABI as T.
    // <https://doc.rust-lang.org/core/mem/union.MaybeUninit.html#layout-1>
    //
    // If all MaybeUninits are initialized then it is safe to
    // transmute a [MaybeUninit<T>; N] to [T; N].
    //
    // All items are initialized because we assert that out_i is exactly LEN.
    // Each out_i (except the last out_i value) means that the MaybeUninit at that index is initialized.
    //
    // Similar to:
    // <https://doc.rust-lang.org/core/mem/union.MaybeUninit.html#initializing-an-array-element-by-element>
    //
    // `transmute_copy` is necessary to ignore the size check.
    // The Rust compiler considers `[T; LEN]` a "dependent" type because of `LEN`
    // and doesn't try to verify it's the same size as `[U; LEN]`.
    #[expect(unsafe_code, reason = "see comment")]
    Ok(unsafe { core::mem::transmute_copy(&out) })
}

I'm a complete newbie to unsafe, so this code might be wrong! I did use miri to test it out with a few inputs and nothing went wrong. I was also told the unnecessary copy transmute_copy would be optimized away (discussed some of the code on the Discord), but I can't verify that.

Metadata

Metadata

Assignees

No one assigned

    Labels

    featureNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions