Skip to content

General purpose concat_slices function #7

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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