General purpose `concat_slices` function
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.
Hi, thank you for opening an issue.
Question, where would you imagine using this function? What is your use case? I see the following problems with it:
- In my experience slices of
&Tare uncommon in constants, at least I haven't seen them used much. - Having to calculate
LENis cumbersome.
Question, where would you imagine using this function? What is your use case?
- It doesn't require
Copy(I need this). - It can be used to concatenate an existing slice.
Having to calculate LEN is cumbersome.
That's a tradeoff for the aforementioned point 2.
Basically, this already exists in constcat in some capacity (concat), it's just not public. Well, it is public, but hidden. This would just make it proper, and also add the ability to use non-Copy types.
- References implement
Copy. So if you have a slice of references toTyou can already use the existing macro. Example:
use constcat::concat_slices;
struct T(i32); // not copy
const TEST123: &[&T] = concat_slices!([&T]: &[&T(1)], &[&T(2), &T(3)]);
I'm concatenating arrays with owned data to a slice with references to the data. I can convert an array to a slice easily, but not the owned values to references. It's true that I could make a function to do that, but it would be nice if the function/macro just accepted &[T]s instead of me converting each one.