&'a [T; N] from &'a [T]: why exact length?

The TryFrom implementation to convert from a borrowed slice to a borrowed array requires that the length is exact:

impl<'a, T, const N: usize> TryFrom<&'a [T]> for &'a [T; N] {
    type Error = TryFromSliceError;

    fn try_from(slice: &[T]) -> Result<&[T; N], TryFromSliceError> {
        if slice.len() == N {
            let ptr = slice.as_ptr() as *const [T; N];
            // SAFETY: ok because we just checked that the length fits
            unsafe { Ok(&*ptr) }
        } else {
            Err(TryFromSliceError(()))
        }
    }
}

(https://doc.rust-lang.org/stable/src/core/array/mod.rs.html#203-215)

Why? Wouldn't it be fine to perform the conversion even if the length of the slice is greater than the length of the requested array? I would expect slice.len() >= N rather than == N.

I suppose the reasoning is, roughly, that From-implementations (or in this case TryFrom-implementations) ought to use their full argument, not ignore parts of it. Or put differently, at least some people will probably expect that &[T] converted to &[T; N] converts the whole thing, and in this case, the conversion as-is will catch more mistakes. There’s other possible ways to truncate an overlength slice; you can take the beginning, or the end, or something in the middle; the method name doesn’t specify so it’s best to do none of these.


Not saying that any of this is the reason, that’s just my own guesswork / intuition. By the way, this doesn’t stop you from achieving the behavior you’re after; just use

let y: &[T; N] = x[..N].try_into().unwrap();

instead of

let y: &[T; N] = x.try_into().unwrap();
4 Likes

just use

let y: &[T; N] = x[..N].try_into().unwrap();

Genius! Thank you. That makes perfect sense.

Sure. There certainly wouldn't be any "that would be unsound" problems with it.

There's a bit of rationale in the FCP from 2017 on the PR that added this: Implement TryFrom<&[T]> for &[T; N] by nvzqz · Pull Request #44764 · rust-lang/rust · GitHub

I would say there's a number of reasons:

  1. Froms that lose information are generally an anti-pattern, and thus Ok from TryFrom should also not lose information. For example, we could have u16: From<u32>, but that loses information so we don't.
  2. It's much more convenient to index the slice inline (as @steffahn showed) than to add a check for the length.
  3. If it's not the whole thing, then the question of which end comes up. And there's no .try_into_but_the_back(), so to me it makes more sense to have TryInto be the whole thing, and add dedicated methods to get the non-whole thing -- Add slice::{split_,}{first,last}_chunk{,_mut} by clarfonthey · Pull Request #95198 · rust-lang/rust · GitHub proposes first_chunk/last_chunk, for example.
  4. zip is notorious for accidental logic errors when things turn out to not be the same length, and thus a few elements are accidentally silently ignored from one iterator or the other, resulting in things like Itertools::zip_eq. So Rust now generally will insist on things matching up, to help catch such things -- that's why copy_from_slice, for example, insists on it. (The docs there even mention using [..N] if you want to allow the mismatch -- which has the advantage that you can decide which of the slices you're allowing the mismatch on.)
2 Likes

Note that x[..N] panics if somehow x.len() < N.

1 Like