rust-lang / libs-team

The home of the library team
Apache License 2.0
127 stars 19 forks source link

ACP: Add `try_from_slice` constructor to `core::array`. #496

Open bjoernager opened 15 hours ago

bjoernager commented 15 hours ago

Proposal

Problem statement

In Rust, you can convert a slice &[T] to an array [T; N] using <[T; N] as TryFrom<&[T]>>::try_from. The main issue with this is that it depends on a trait function, and trait functions are not allowed in constant expressions (e.g. const fn).

I propose adding a try_from_slice function to the core::array module for this scenario. The module in question already defines other constructors such as from_fn and from_ref.

Solution sketch

The following function should be added to the standard library:

// core::array

pub const fn try_from_slice<T, const N: usize>(slice: &[T]) -> Result<[T; N], TryFromSliceError>
where
    T: Copy;

Alternatives

The main alternative to adding this feature (with regard to const) would be to allow trait functions in constant expressions. This is already covered by const_trait_impl.

I still do believe adding this function can improve clarity in some code. Other areas of the standard library already define similar patterns, e.g. String implements Into<Box<str>> whilst also defining the into_boxed_str destructor.

Links and related work

Initial, unstable implementation: #133439

kennytm commented 14 hours ago

If we are adding a dedicated conversion function I think flipping the order turning it into an inherent method of the slice is more convenient to the users

impl<T> [T] {
    pub const fn as_array_exact<const N: usize>(&self) -> Result<&[T; N], TryFromSliceError>;
}

I'm not sure why the ACP proposed &[T] -> [T; N] thus requiring T: Copy. Dereferencing a &[T; N] is allowed in const context.

bjoernager commented 13 hours ago

I see your point, although I feel it would be confusing to have the error type named TryFromSliceError when returned from a function with a slightly different intention and scope. Defining a new type AsArrayExactError would be clearer but also result in mostly duplicate code.

As for your second concern, I'm not quite sure I understand what the problem is. The semantics proposed in this ACP are basically the same as the preexisting; the logic has just been moved from TryFrom to a global function, using the same infrastructure. This approach is minimally different from status quo.

programmerjake commented 9 hours ago
impl<T> [T] {
    pub const fn as_array_exact<const N: usize>(&self) -> Result<&[T; N], TryFromSliceError>;
}

I think as_array is clear enough, _exact isn't needed. also, const fn supports &mut now, so there should also be an as_array_mut function.

jdahlstrom commented 5 hours ago

For what it's worth, in the meantime this works in a const context, though only on nightly for now:

if let ([a], []) = s.as_chunks() {
    Some(a)
} else {
    None
}
scottmcm commented 5 hours ago
impl<T> [T] {
    pub const fn as_array<const N: usize>(&self) -> Option<&[T; N]>;
    pub const fn as_array_mut<const N: usize>(&mut self) -> Option<&mut [T; N]>;
}

seems pretty reasonable to me.

Would be convenient to have it in const, and the TryFrom impl can use it with a .ok_or(TryFromSliceError).

(I don't think it needs to return a Result, because the error can't say anything meaningful -- just like how NonZero::new returns an Option, rather than having a IsNotZeroError.)

bjoernager commented 5 hours ago

I'm personally content with that solution.