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.