Slice to fixed-size array [duplicate]

2020-02-10 15:51发布

问题:

I have a structure with some fixed-sized arrays:

struct PublicHeaderBlock_LAS14 {
    file_signature: [u8; 4],
    file_source_id: u16,
    global_encoding: u16,
    project_id_data_1: u32,
    project_id_data_2: u16,
    project_id_data_3: u16,
    project_id_data_4: [u8; 8],
    version_major: u8,
    version_minor: u8,
    systemIdentifier: [u8; 32], // ...
}

I'm reading in bytes from a file into a fixed size array and am copying those bytes into the struct bit by bit.

fn create_header_struct_las14(&self, buff: &[u8; 373]) -> PublicHeaderBlock_LAS14 {
    PublicHeaderBlock_LAS14 {
        file_signature: [buff[0], buff[1], buff[2], buff[3]],
        file_source_id: (buff[4] | buff[5] << 7) as u16,
        global_encoding: (buff[6] | buff[7] << 7) as u16,
        project_id_data_1: (buff[8] | buff[9] << 7 | buff[10] << 7 | buff[11] << 7) as u32,
        project_id_data_2: (buff[12] | buff[13] << 7) as u16,
        project_id_data_3: (buff[14] | buff[15] << 7) as u16,
        project_id_data_4: [buff[16], buff[17], buff[18], buff[19], buff[20], buff[21], buff[22], buff[23]],
        version_major: buff[24],
        version_minor: buff[25],
        systemIdentifier: buff[26..58]
    }
}

The last line (systemIdentifier) doesn't work, because in the struct it is a [u8; 32] and buff[26..58] is a slice. Can I return convert a slice to a fixed sized array like that over a range, instead of doing what I've done to say file_signature?

回答1:

There is no safe way to initialize an array in a struct with a slice. You need either resort to unsafe block that operates directly on uninitialized memory, or use one of the following two initialize-then-mutate strategies:

Construct an desired array, then use it to initialize the struct.

struct Foo {
    arr: [u8; 32],
}

fn fill(s: &[u8; 373]) -> Foo {
    let mut a: [u8; 32] = Default::default();
    a.copy_from_slice(&s[26..58]);
    Foo { arr: a }
}

Or initialize the struct, then mutate the array inside the struct.

#[derive(Default)]
struct Foo {
    arr: [u8; 32],
}

fn fill(s: &[u8; 373]) -> Foo {
    let mut f: Foo = Default::default();
    f.arr.copy_from_slice(&s[26..58]);
    f
}

The first one is cleaner if your struct has many members. The second one may be a little faster if the compiler cannot optimize out the intermediate copy. But you probably will use the unsafe method if this is the performance bottleneck of your program.



回答2:

Thanks to @malbarbo we can use this helper function:

use std::convert::AsMut;

fn clone_into_array<A, T>(slice: &[T]) -> A
    where A: Sized + Default + AsMut<[T]>,
          T: Clone
{
    let mut a = Default::default();
    <A as AsMut<[T]>>::as_mut(&mut a).clone_from_slice(slice);
    a
}

to get a much neater syntax:

fn main() {
    let original = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

    let e = Example {
        a: clone_into_array(&original[0..4]),
        b: clone_into_array(&original[4..10]),
    };

    println!("{:?}", e);
}

as long as T: Default + Clone.

It will panic! if the target array and the passed-in slice do not have the same length, because clone_from_slice does.