How to convert `[T; n]` to `[T]` and vice-versa?

Hey all, and happy new year!

I'm writing a program for embedded systems that needs to seed an HC-128 random number generator. I'm generating a seed from the hardware, but this breaks:

Hc128Rng::from_seed(*seeds.concat().as_slice())

Hc128Rng::from_seed needs a [u8; 32], but I'm passing in an [u8]. What confuses me is I don't see how either of these are "distinct" types. They're exactly the same; the only difference is that one has an explicit length in its definition and the other doesn't. But both are slices. My random seed generation gave me [u8; 8] from u64::to_ne_bytes. This also fails:

Hc128Rng::from_seed(seeds.iter().flatten().cloned().collect())

What is the "correct" and "idiomatic" way of converting [T] slices to [T; n] and vice-versa?

I think .as_slice() returns an &[u8], which is a (shared = immutable) reference. I don't know what Hc128Rng expects exactly. [u8; 32] is an array, while &[u8] is a slice. Those are two distinct types. See below.

fn foo(ary: [i32; 4]) {
    println!("{:?}", ary);
}

fn main() {
    let ary: [i32; 4] = [5, 6, 20, 99];
    let slice: &[i32] = ary.as_slice();
    //foo(slice); // this won't work!
    drop(slice);
    foo(ary);
}

(Playground)

Also note that a function cannot return [u8] or accept [u8] as argument, because that is unsized. It would require a Box (or a reference/slice, such as &[u8]).

Maybe someone else can give better help on this issue though.

[T] is a slice, and has no static size. [T; N] is an array, not a slice, and does have a static size. You can coerce a reference to an array into a reference to a slice (&[T]), but they are distinct types; the size being statically known or not makes a big difference.

(There can be some confusion in this area as "slice" is casually used to refer both to [T] and &[T]; the latter is more properly a shared slice or reference to a slice.)

Anyway, to your question. Since you're just dealing with u8, the simplest way to go from a shared slice (&[u8]) to an owned array ([u8; 32]) is probably .try_into():

fn f(slice: &[u8]) {
    let arr: [u8; 32] = slice.try_into().expect("Wrong size");
}

However, you're not going to be able to get a [u8; 32] out of a single [u8; 8]. If seeds is a [u64; 4], you could use bytemuck::cast, or make use of the other functions in that crate.

If you'd rather do things manually (but safely), you could start with a [0; 32], and iterate over that and your source data, copying over the bytes.

fn to_seed_data<I: IntoIterator<Item=u64>>(iter: I) -> [u8; 32] {
    let mut count = 0;
    let mut data = [0; 32];
    for (byte, destination) in iter
        .into_iter()
        .flat_map(u64::to_ne_bytes)
        .zip(data.iter_mut())
    {
        count += 1;
        *destination = byte;
    }
    
    assert_eq!(count, 32, "not enough seed data");
    data
}

Playground.

1 Like

Oh, I didn't know about this use of terminology. The doc of the standard lib regarding slices is a bit confusing in that matter:

Primitive type slice
A dynamically-sized view into a contiguous sequence, [T]. […]
[…]
Slices are either mutable or shared. The shared slice type is &[T], while the mutable slice type is &mut [T] , where T represents the element type.

I would say that [T] refers to "contiguous sequence" and not to "slice". Thus I would say, the slice isn't [T] but the (dynamically-sized) view into it (which can be &[T] or &mut [T]).

Of course, this is contradicting with Box<[T]> being referred to as "boxed slice". Instead it would have to be called "boxed contiguous sequence" or "boxed array". In that case, the documentation of Vec::into_boxed_slice would have a wrong name (as it would need to be into_boxed_array). The same holds for the Box::into_boxed_slice method, which is unstable yet.

Either the documentation for the primitive type slice seems to be wrong, or the commonly used term "boxed slice". (Or I'm misunderstand the wording in the documentation.)


On the other hand, the documentation on arrays says something different:

Arrays coerce to slices ([T]), so a slice method may be called on an array. Indeed, this provides most of the API for working with arrays. Slices have a dynamic size and do not coerce to arrays.

Here, a slice refers to [T]. Confusing. :face_with_raised_eyebrow:

Thank you -- I understand the difference now.

Those docs are being casual, using the same term for the DST and the references (which are not dynamically sized -- they're the size of two usizes). Mostly they're talking about the latter. They're also sloppy in other ways too -- slices can be owned (as part of a coerced type, or in something like a Box).

To be fair, the references existed before DSTs proper. But [T] being a slice is very well established now. E.g.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.