首页 > 解决方案 > 如何有效地从计算的部分构建一个字节数组?

问题描述

我需要构建一个表示设备命令的字节数组。它可能看起来像这样:

let cmds = [
    0x01, // cmd 1
    0x02, // cmd 2
    0x03, 0xaa, 0xbb, // cmd 3
    0x04, // cmd 4
    0x05, 0xaa, // cmd 5
];

有些命令带参数,有些不带。有些参数需要计算。每个命令的大小都是固定的,因此在编译时就知道数组需要多大。

像这样构造它会很好,我将字节组抽象为命令:

let cmds = [
    cmd1(),
    cmd2(),
    cmd3(0, true, [3, 4]),
    cmd4(),
    cmd5(0xaa)
];

我还没有找到任何方法来使用函数或宏来做到这一点。我在no_std,所以我不使用集合。

如何在 Rust 中实现类似的东西?

标签: rustmacrosrust-no-std

解决方案


您可以让每个命令函数返回一个数组或Vec字节:

fn cmd1() -> [u8; 1] { [0x01] }
fn cmd2() -> [u8; 1] { [0x02] }
fn cmd3(_a: u8, _b: bool, _c: [u8; 2]) -> [u8; 3] { [0x03, 0xaa, 0xbb] }
fn cmd4() -> [u8; 1] { [0x04] }
fn cmd5(a: u8) -> Vec<u8> { vec![0x05, a] }

然后像这样构建你的命令:

let cmds = [
    &cmd1() as &[u8],
    &cmd2(),
    &cmd3(0, true, [3, 4]),
    &cmd4(),
    &cmd5(0xaa),
];

这构建了一个字节切片数组。要获取完整的字节流,请使用flatten

println!("{:?}", cmds);
println!("{:?}", cmds.iter().copied().flatten().collect::<Vec<_>>());
[[1], [2], [3, 170, 187], [4], [5, 170]]
[1, 2, 3, 170, 187, 4, 5, 170]

您可以通过返回一些实现Command特征的类型并将它们收集到特征对象数组中来使这一点更加精细,但我将把它留给 OP。


编辑:arrayvec这是一个可以使用crate直接构建数组的宏:

use arrayvec::ArrayVec;

fn cmd1() -> [u8; 1] { [0x01] }
fn cmd2() -> [u8; 1] { [0x02] }
fn cmd3(_a: u8, _b: bool, _c: [u8; 2]) -> [u8; 3] { [0x03, 0xaa, 0xbb] }
fn cmd4() -> [u8; 1] { [0x04] }
fn cmd5(a: u8) -> [u8; 2] { [0x05, a] }

macro_rules! combine {
    ($($cmd:expr),+ $(,)?) => {
        {
            let mut vec = ArrayVec::new();
            $(vec.try_extend_from_slice(&$cmd).unwrap();)*
            vec.into_inner().unwrap()
        }
    }
}

fn main() {
    let cmds: [u8; 8] = combine![
        cmd1(),
        cmd2(),
        cmd3(0, true, [3, 4]),
        cmd4(),
        cmd5(0xaa),
    ];
    
    println!("{:?}", cmds);
}

如果您担心性能,此示例将数组编译为一条指令:

movabs  rax, -6195540508320529919 // equal to [0x01‬, 0x02, 0x03, 0xAA, 0xBB, 0x04, 0x05, 0xAA]

在操场上看到它。它仅限于Copy. 必须提供数组的长度。如果数组大小与结果的组合大小不匹配,它将在运行时恐慌。


推荐阅读