Feature Request: allow partial move of const arrays

I was thinking how const array slices could be concated together (See playground), and I found it possible only to merge whole const arrays. But I'd also, as an owner of a [T;N] would like to get an owned "slice" [T;M] where 1 <= M <= N and throw away all the N-M array positions that I don't care about. I believe this could allow the use case of concating arbitrary slices of const arrays.

So the following should be valid:

let x: [u32; 3] = [1,2,3];
let y: [u32;2] = x[0..=1];

And x becomes a partially moved value.

Currently there is an error (first that y's type is invalid) and second that y doesn't have a size known at compile time!!! Which is not true! We know y's length and y's inner type, and we become the owners of the array.

Safety

If T is not Copy, then of course the following should be invalid and throw an error at compile time (thanks @SkiFire13):

let x: [String; 4] = ["hello".into(), "there".into(), "General".into(), "Kenobi".into()];
let y: [String; 2] = x[0..=1];
x[0]; x[1];

While allowing x[2] and x[3].

A couple of observations:

  • How would this play with const generics? e.g. if x: [T; N] and I do x[0..=1], does it compile? If it does, I guess it will have to panic at runtime if N < 2. If it doesn't, how do we express the contraint N >= 2? Do we have to wait for full const generics and bounds support?
  • Should it work only with literals or also consts? What about calls to const fns with const/literals parameters? AFAIK we currently don't allow calls to const fns in patterns due to possible confusion, so it is blocked on const blocks and patterns.
  • This could be pretty confusing for beginners which start with defining arrays on the stack and can slice them, but the moment they start using slices/vecs/non-literal indexes they, but I guess this is nothing that can't be solved with better diagnostics.
  • This would mean range indexing would become a compiler intrinsic (right now it's implemented with traits, and only normal usize indexing is an intrinsic).
  • A positive side-effect is that this would allow slicing arrays in const fns (since currently it is implemented as a trait it is not available in const contexts)

Note that since u32 is Copy this code would be valid. If it was String or some other non-Copy type then you would be right that it shouldn't compile.

1 Like

This probably is a wild idea, but I would assume that you could write a where N>2 clause (or even better, it would be autogenerated) next to the definition of the const generic, and then on instantiation of N you would get a compiler error?

The rest goes over my head, sorry.

you can already (on nightly) kind of express N > 2

#![feature(const_generics)]
#![feature(const_evaluatable_checked)]

fn foo<T, const N: usize>(src: &[T; N]) -> &T 
where
    [(); N - 2]: Sized,
{
    unsafe { src.get_unchecked(1) }
}

fn main() {
    let arr = [0, 2, 3, 4];

    assert_eq!(&2, foo(&[0, 2, 4, 6]));
    // assert_eq!(&2, foo(&[1])); // compile error!
}

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=ec299e1c38243a902f90035e5ceb37f9

Now that I think about it, another option would be using patterns. Currently you can already do:

let x: [String; 4] = ["hello".into(), "there".into(), "General".into(), "Kenobi".into()];
let [y @ .., x2, x3] = x;

However the downsides are that you can only have one subarray and you need to have a pattern (either a name or _) for all the other elements.

2 Likes

However the downsides are that you can only have one subarray and you need to have a pattern (either a name or _ ) for all the other elements.

Yeah so it only works if you know the number of elements beforehand. But still cool.