将切片转换为固定大小的数组

26

我有一个带有一些固定大小数组的结构:

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], // ...
}

我正在从文件中以字节为单位读取数据,将其复制到固定大小的数组中,并将这些字节逐位地复制到结构体中。

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]
    }
}

最后一行(systemIdentifier)无效,因为在结构体中它是一个[u8; 32],而buff[26..58]是一个切片。我可以将一个切片转换为一个固定大小的数组,并覆盖指定范围内的值,而不是像对file_signature所做的那样手动赋值吗?


我一直在尝试创建一个make_array<A>(slice: &[T]) -> A ...但到目前为止,我还没有成功。 - Matthieu M.
我在 https://dev59.com/Q5fga4cB1Zd3GeqPA8vU 上询问是否有人知道如何使这个辅助函数工作,它将极大地简化这里的事情。 - Matthieu M.
2个回答

24

编辑:自从Rust 1.34版本以来,您可以使用TryInto,它派生自TryFrom<&[T]> for [T; N]

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

fn fill(s: &[u8; 373]) -> Foo {
    // We unwrap here because it will always return `Ok` variant
    let arr: [u8; 32] = s[26..68].try_into().unwrap();
    Foo { arr }
}

2016年的原始答案:

没有一种安全的方法可以使用slice初始化结构体中的数组。你需要使用以下两种初始化-然后修改的策略之一,要么使用直接操作未初始化内存的unsafe块,要么使用以下策略:

构造所需的数组,然后使用它来初始化结构体。

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 }
}

或者初始化结构体,然后在结构体内部修改数组。

#[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
}

如果您的结构体具有许多成员,则第一种方法更加清晰。如果编译器无法优化中间副本,则第二种方法可能会稍微快一些。但是,如果这是您程序的性能瓶颈,您可能会使用unsafe方法。


谢谢您详细的回答 :) 我选择了安全的方式,因为性能对于我的库的这一部分并不是必要的 - 只需要加载/解析不同的文件。 - user1993515

5

感谢@malbarbo,我们可以使用这个辅助函数:

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
}

为了获得更整洁的语法:

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);
}

只要实现了 T: Default + Clone,它就能运行。如果目标数组和传入的切片长度不同,clone_from_slice 会导致程序崩溃。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接