One implementation possibility is to have the slice-returning version and then just wrap it in a const-generic version which does the TryFrom.
That's the same as (&array[..index]).try_into(). I'm pretty sure that try_into() also performs a memcpy(), so the behavior is not that different from truncate.
Any time you have a by-value function argument/return, it's logically a copy, and you're at the mercy of copy elision to remove them. There's nothing more you can do.
Btw, here's a trick to optimize concat that also makes it const-compatible:
const fn concat<T, const N: usize, const M: usize>(
lhs: [T; N], rhs: [T; M],
) -> [T; { N + M }] {
#[repr(C)]
struct Concat<LHS, RHS>(LHS, RHS);
unsafe { transmute(Concat(lhs, rhs)) }
}
and I'd currently write truncate as
fn truncate<T, const LESS: usize, const MORE: usize>(
arr: [T; MORE],
) -> [T; LESS]
where
const { LESS <= MORE },
{
let arr = ManuallyDrop::new(arr);
// optional: to avoid leaking at greater risk of abort:
// consider also a manual guard defuse
let arr = scopeguard::guard_on_unwind(arr, |arr| {
// NB: tail is already dropped
unsafe { drop_in_place(&mut arr[..LESS]) };
});
unsafe {
drop_in_place(&mut arr[LESS..]);
transmute_copy(&arr)
}
}
(I don't know if where const is implemented yet.)
Or perhaps alternatively to make the drop semantics more standard via being implicit,
fn split_at<T, const MID: usize, const LEN: usize>(
arr: [T; LEN],
) -> ([T; MID], [T; { LEN - MID }])
where
const { MID <= LEN },
{
let arr = ManuallyDrop::new(arr);
// TODO: check if unwrap_unchecked is needed
let lhs = arr[..MID].try_into().unwrap();
let rhs = arr[MID..].try_into().unwrap();
(lhs, rhs)
}
fn truncate<T, const LESS: usize, const MORE: usize>(
arr: [T; MORE],
) -> [T; LESS]
where
const { LESS <= MORE },
{
split::<T, LESS, MORE>(arr).0
}
I really like these implementations, I'll implement them in the repo (with some minor changes).
I guess transmute_copy() would do the work as it doesn't check the sizes.
this works
I based that off of memory from a version that concatenates const strings. Pulling it up, it does use a custom transmute_prefix to sidestep the dependent sizes check.
I see you're doing impl<T> and impl<T: Drop> — this is absolutely not what you want. A T: Drop bound is never what you want.
As a trait bound, T: Drop holds when an explicit implementation of Drop exists. If you have something like struct S { list: Vec<Data> } and no explicit impl Drop for S, then S: Drop does not hold.
If you want to specialize behavior for types without drop glue, you need to use T: Copy (for a static guarantee) or needs_drop (for a monomorphized check).
But for this case, just call drop_in_place::<[T]>. It will trivially monomorphize to nothing when instantiated for a T which is !needs_drop::<T>(). (If you're extremely paranoid, call drop_in_place::<[T; N]>.)
Hmmmm, I specialized over Drop because drop_in_place() is not const and everything else was, but I think I can invert the implementations and specialize over Copy, defaulting to the Drop impl.
The pop operation is already possible with destructuring:
let [..rest, tail] = array;
I don't know how efficient this is compared to the proposed method, but if it isn't as efficient, I think it can be further optimized.
For the push operation, I'd much rather have a built-in syntax that looks symmetrical to destructuring:
let new_array = [..array, new_elem];
In JS, this is called spread syntax. Rust has something similar (the struct update syntax), but currently only for structs and enum variants.
It's worth noting that this causes a lot of issues, because the pattern spread syntax is the same as range constructor syntax.
So [..array, new_elem] is [RangeTo::new(array), new_elem].
That's not to say that this isn't resolvable, but it is an issue.
Maybe we can do 3 dots like this for append:
let array = [...old_array, element1, element2];
And this for concat:
let array = [...old_array1...old_array2, element1, element2];`
With the possibility of combining them:
let array = [...old_array1...old_array2...old_array3, element1, element2, element3];`
For truncate we have destructuring assigments.
Once i get home I'll power on my laptop and write a language design post.
It looks like you could replace both methods with a generic implementation of std::ops::Add<[T;N]> for [T;L].
It has already been discussed in the thread linked above, such implementation would be ambiguous between concatenation and elementwise addition.
Huh, and right above my post too... Looks like something glitched - I only saw the post before that one.
