Slice.take function

I was writing something like

    fn take_10<T>(slice: &[T]) -> Result<&[T; 10], IndexOutOfRangeError> {
        ...
    }

Do we have to wait for const generics for something like the above to be in std?

You mean, a similar function that is generic over the length of the returned array?

That would indeed require const generics. Whether a function like this should be added to std, however, is a very different question.

Technically I only need something similar in std for the length limited to 0-32.

And why does it have to be in std?

I don’t know a way to do this without unsafe trick or sacrifice performance (extra copy for example). If there is a way can achieve this I would not ask for it.

Note that you can use TryFrom on nightly to do this.

use std::convert::TryFrom;
use core::array::TryFromSliceError;

fn take_2<T>(x: &[T]) -> Result<Option<&[T; 2]>, TryFromSliceError> {
    x.get(0..2).map(<&[T; 2]>::try_from).transpose()
}

fn take_2_mut<T>(x: &mut [T]) -> Result<Option<&mut [T; 2]>, TryFromSliceError> {
    x.get_mut(0..2).map(<&mut [T; 2]>::try_from).transpose()
}

TryFrom<&[_]> is implemented for all arrays with less than 32 elements.

5 Likes

Actually, I believe it’s impossible. You want to return a reference to an array containing 10 elememts of a slice. However, an array must own its data, since it frees its memory when it goes out of scope. The slice doesn’t own its data, so you can’t put it into an array without cloning it.

Right. So we still have to wait for this to land stable.

But I guess TryFrom is arguably a redundant trait. An alternative is to introduce "simple wrapper"s for a set of types that can bypass the orphan rule, so we can simply write

impl From<[&T]> for Option<&[T; 10]>{}

How to define "simple wrapper" is the problem. I would like both Option and Result be in this set.

One possiblity would be:

A type is a "simple wrapper" if it does not constraint its type parameter in the definition, and all trait implementations in the defining crate constrains all its type parameters implements the same trait.

For a "simple wrapper" type, we can then relax the orphan rule to allow the case that all the component types belong to the same implementing crate.

He wants a reference to a fixed-sized array, not a fixed-sized array. So this is fine.

3 Likes

unsafe version that is correct and works now

const N: usize; // you pick (could be macro generated)

fn take_n<T>(slice: &[T]) -> Option<&[T; N]> {
    if slice.len() < N {
        None
    } else {
        unsafe { &*(slice as *const [T] as *const [T; N]) }
    }
}

fn take_n_mut<T>(slice: &mut [T]) -> Option<&mut [T; N]> {
    if slice.len() < N {
        None
    } else {
        unsafe { &mut *(slice as *mut [T] as *mut [T; N]) }
    }
}
2 Likes

I guess you’re right.

But your function returning a mutable array reference violates the ownership rules (it can lead to data races)!

EDIT: I’m wrong, and you are right. I thought that there would be two owners of the data after calling take_n_mut.

How? This is semantically similar to split_at_mut in std.


While looking over the code, I realized that my copy-pasting went bad. (Not that it takes away from the idea of the code)

1 Like

Here is a more generic version of the take_n function:

mod impls_for_array_{
    mod sealed{
        pub trait Sealed{}
    }
    use self::sealed::Sealed;

    pub trait Array:Sealed{
        type Elem;
        const SIZE:usize;
    }

    macro_rules! impls_for_array {
        ($($size:expr),*)=>{
            $(
                impl<T> Sealed for [T;$size] {}
                
                impl<T> Array for [T;$size] {
                    type Elem=T;
                    const SIZE:usize=$size;
                }
            )*
        }
    }

    impls_for_array! {
        00,01,02,03,04,05,06,07,08,09,
        10,11,12,13,14,15,16,17,18,19,
        20,21,22,23,24,25,26,27,28,29,
        30,31,32
    }
}
pub use self::impls_for_array_::Array;


fn take_n<A>(slice: &[A::Elem]) -> Option<&A> 
where A:Array
{
    if slice.len() < A::SIZE {
        None
    } else {
        Some(unsafe { &*(slice as *const [A::Elem] as *const A) })
    }
}

fn take_n_mut<A>(slice: &mut [A::Elem]) -> Option<&mut A> 
where A:Array
{
    if slice.len() < A::SIZE {
        None
    } else {
        Some(unsafe { &mut *(slice as *mut [A::Elem] as *mut A) })
    }
}


fn main() {
    assert_eq!(take_n(&[1,2,3,4]),Some(&[]) );
    assert_eq!(take_n(&[1,2,3,4]),Some(&[1]) );
    assert_eq!(take_n(&[1,2,3,4]),Some(&[1,2]) );
    assert_eq!(take_n(&[1,2,3,4]),Some(&[1,2,3]) );
    assert_eq!(take_n(&[1,2,3,4]),Some(&[1,2,3,4]) );
    assert_eq!(take_n::<[_;10]>(&[1,2,3,4]),None );
}
1 Like

And with method notation:

pub
trait AsArray
{
    type Item : Sized;

    fn as_array<A : Array<Item = Self::Item>> (
        self: &'_ Self,
    ) -> Option<&'_ A>;

    fn as_array_mut<A : Array<Item = Self::Item>> (
        self: &'_ mut Self,
    ) -> Option<&'_ mut A>;
}

impl<Item : Sized> AsArray for [Item]
{
    type Item = Item;

    #[inline]
    fn as_array<A : Array<Item = Item>> (
        self: &'_ Self,
    ) -> Option<&'_ A>
    {
        if self.len() < A::LEN { return None; }
        Some(unsafe {
            & *(self.as_ptr() as *const A)
        })
    }

    #[inline]
    fn as_array_mut<A : Array<Item = Item>> (
        self: &'_ mut Self,
    ) -> Option<&'_ mut A>
    {
        if self.len() < A::LEN { return None; }
        Some(unsafe {
            &mut *(self.as_mut_ptr() as *mut A)
        })
    }
}

EDIT: Item is now an associated type as per @RustyYato’s remark

Shouldn’t Item be an associated type instead of a parameter?

1 Like

The arrayref crate has a different macro-based solution to this problem.

2 Likes

yep, good catch

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