`slice` lacks `get_array_ref` functionality

I noticed that impl<T> [T] implements get function. This allow to get elements in a range as slice. For example [200,200,0,0,1,1].get(..4), unfortunately, the return type is a slice, rather than array ref.

I'm looking for a way, to get this slice as a fixed size array ref. Ideally I would like to write the code this like

let _: Option<[u8; 4]> = [200,200,0,0,1,1].get_array_ref<4>(0);
let _: Option<[u8; 1]> = [200,200,0,0,1,1].get_array_ref<1>(4);

Current get api looks like this:

   pub fn get<I>(&self, index: I) -> Option<&I::Output>  where   I: SliceIndex<Self>;

It would be a good idea to add the following api.

#[lang = "slice"]
#[cfg(not(test))]
impl<T> [T] {
    pub fn get_array_ref<const N: usize>(&self, index: usize) -> Option<&[T; N];
}

&[T; N] implements TryFrom<&[T]>, so you can use that (or the equivalent TryInto impl) to get an array ref from a slice (though you have to first slice it to the right length).

Yes, technically, it's possible to write it as a single line:

   let _x: Option<&[u8; 4]> =
            [200u8, 200, 0, 0, 1, 1].get(..4).map(|x| <&[u8; 4]>::try_from(x).ok()).flatten();

That's a big ugly though.

You don't need the type annotation and map + flatten on an Option can be simplified to just and_then. So you can get away with less boilerplate, though of course a dedicated method would still be shorter.

Your proposed API is a index-length API. While this is unavoidable (since the length needs to be const, and the index shouldn't need to be), it's definitely not immediately clear.

A currently available unstable option is array_windows.

fn get_array_ref<const N: usize>(this: &[T], index: usize) -> Option<&[T; N]> {
    this.array_windows::<N>().nth(index)
}

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=17817ba390a3ef938e3d07076676630d

1 Like

There's a wide variety of unstable array-returning functions in nightly, as this is still an evolving area.

For example, one way to do that would be .as_chunks().0.get(0).

1 Like

Is it possible to do this? (But might require more functioning of const generics)

 // Make literals of range to be `ConstRange`, and allow it coerce to `Range<uize>`.
ConstRange<Idx, const S: Idx, const E: Idx>;

impl<T, const N: usize, const S: usize, const E: usize> SliceIndex<[T; N]>
    for ConstRange<usize, S, E>
where
    S < E,
    S < N,
    E <= N,
{
    type Output = [T; E - S];
    fn index(self, slice: &[T; N]) -> &Self::Output {
        unsafe { self.get_unchecked(slice)) as &[T; E - S] }
    }
    unsafe fn get_unchecked(self, slice: *const [T; N]) -> *const Self::Output {
        slice.as_ptr().add(S) as *const [T; E - S]
    }
    // impl other functions similarly
}

And then, it is possible to use it like this

let arr = [0, 1, 2, 3, 4];
let sub_array: [i32; 2] = arr[0..2];

With adt_const_params you can use Range itself. But to return an array based on that requires the full generic_const_exprs feature. And using every possible range (Range, RangeInclusive, RangeTo, etc.) isn't possible even then, unless we'll have some kind of generics-over-const-parameters (i.e. fn foo<T: Trait, const C: T>()).

#![feature(adt_const_params, generic_const_exprs)]

use std::ops::Range;

pub const fn range_size(range: Range<usize>) -> usize {
    range.end - range.start
}

pub fn slice_get_array<T, const RANGE: Range<usize>>(slice: &[T]) -> &[T; range_size(RANGE)] {
    unsafe { &*(&slice[RANGE] as *const [T] as *const _) }
}

Playground.

1 Like

It's cool! But it could be even better if your version fn slice_get_array can be put into a trait so that slicing on arrays can get an array, too, if the index Range<usize> is const (instead of a slice).

pub trait SliceExt {
    type Item;
    fn get_array<const RANGE: Range<usize>>(&self) -> &[Self::Item; range_size(RANGE)];
    fn get_array_mut<const RANGE: Range<usize>>(&mut self) -> &mut [Self::Item; range_size(RANGE)];
}

impl<T> SliceExt for [T] {
    type Item = T;
    fn get_array<const RANGE: Range<usize>>(&self) -> &[T; range_size(RANGE)] {
        unsafe { &*(&self[RANGE] as *const [T] as *const _) }
    }
    fn get_array_mut<const RANGE: Range<usize>>(&mut self) -> &mut [T; range_size(RANGE)] {
        unsafe { &mut *(&mut self[RANGE] as *mut [T] as *mut _) }
    }
}

impl<T, const N: usize> SliceExt for [T; N] {
    type Item = T;
    fn get_array<const RANGE: Range<usize>>(&self) -> &[T; range_size(RANGE)] {
        unsafe { &*(&self[RANGE] as *const [T] as *const _) }
    }
    fn get_array_mut<const RANGE: Range<usize>>(&mut self) -> &mut [T; range_size(RANGE)] {
        unsafe { &mut *(&mut self[RANGE] as *mut [T] as *mut _) }
    }
}

fn main() {
    let slice: &[i32] = &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    let arr: &[i32; 3] = slice.get_array::<{ 1..4 }>();
    // let arr: &[i32; 4] = slice.get_array::<{ 1..4 }>();
    dbg!(arr);
}

Playground.

1 Like

Actually, I hope there is let arr: &[i32; 3] = slice[1..4]; in the future (maybe in Rust 2024 or something else).

It is straightforward and all bound checks can be done in compile time.

I'm not even sure that adding a method to the standard library is good (though it can live inside an external crate), but I'm pretty sure that re-using the existing syntax is a very bad idea. Values that affect types? And if later I'll store the range in a variable, will I need to change the type in every place too?

I agree with you. How about making a &[T] convertible (by coercing?) to a &[T; N] in a const context? Since the length of slice must be known to the compiler in a const context.

EDIT: or allow &[T] as &[T; N] if &[T] is const.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.