如何从计算部分有效地构建字节数组?
How to effectively build a byte array from calculated parts?
我需要构建一个字节数组来表示对设备的命令。它可能看起来像这样:
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 中实现类似的东西?
您可以让每个命令函数 return 一个数组或 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]
您可以通过 return 一些实现 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]
在 playground 上查看。它仅限于 Copy
的类型。必须提供数组的长度。如果数组大小与结果的组合大小不匹配,它将在运行时出现恐慌。
如果你把它作为一个宏来做,你可以在没有外部依赖的情况下完成它:
macro_rules! cmd_array {
(@ [ $($acc:tt)* ]) => { [ $($acc)* ] };
(@ [ $($acc:tt)* ] cmd1(), $($tail:tt)*) => { cmd_array!{@ [ $($acc)* 0x01, ] $($tail)* } };
(@ [ $($acc:tt)* ] cmd2(), $($tail:tt)*) => { cmd_array!{@ [ $($acc)* 0x02, ] $($tail)* } };
(@ [ $($acc:tt)* ] cmd3 ($a:expr, $b:expr, $c:expr), $($tail:tt)*) => { cmd_array!{@ [ $($acc)* 0x03, 0xaa, 0xbb, ] $($tail)* } };
(@ [ $($acc:tt)* ] cmd4(), $($tail:tt)*) => { cmd_array!{@ [ $($acc)* 0x04, ] $($tail)* } };
(@ [ $($acc:tt)* ] cmd5 ($a:expr), $($tail:tt)*) => { cmd_array!{@ [ $($acc)* 0x05, $a, ] $($tail)* } };
($($tail:tt)*) => {
cmd_array!(@ [] $($tail)*)
}
}
fn main() {
let cmds: [u8; 8] = cmd_array![
cmd1(),
cmd2(),
cmd3(0, true, [3, 4]),
cmd4(),
cmd5(0xaa),
];
println!("{:?}", cmds);
}
此宏是使用 incremental TT muncher to parse the commands, combined with push-down accumulation 构建最终数组的。
我需要构建一个字节数组来表示对设备的命令。它可能看起来像这样:
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 中实现类似的东西?
您可以让每个命令函数 return 一个数组或 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]
您可以通过 return 一些实现 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]
在 playground 上查看。它仅限于 Copy
的类型。必须提供数组的长度。如果数组大小与结果的组合大小不匹配,它将在运行时出现恐慌。
如果你把它作为一个宏来做,你可以在没有外部依赖的情况下完成它:
macro_rules! cmd_array {
(@ [ $($acc:tt)* ]) => { [ $($acc)* ] };
(@ [ $($acc:tt)* ] cmd1(), $($tail:tt)*) => { cmd_array!{@ [ $($acc)* 0x01, ] $($tail)* } };
(@ [ $($acc:tt)* ] cmd2(), $($tail:tt)*) => { cmd_array!{@ [ $($acc)* 0x02, ] $($tail)* } };
(@ [ $($acc:tt)* ] cmd3 ($a:expr, $b:expr, $c:expr), $($tail:tt)*) => { cmd_array!{@ [ $($acc)* 0x03, 0xaa, 0xbb, ] $($tail)* } };
(@ [ $($acc:tt)* ] cmd4(), $($tail:tt)*) => { cmd_array!{@ [ $($acc)* 0x04, ] $($tail)* } };
(@ [ $($acc:tt)* ] cmd5 ($a:expr), $($tail:tt)*) => { cmd_array!{@ [ $($acc)* 0x05, $a, ] $($tail)* } };
($($tail:tt)*) => {
cmd_array!(@ [] $($tail)*)
}
}
fn main() {
let cmds: [u8; 8] = cmd_array![
cmd1(),
cmd2(),
cmd3(0, true, [3, 4]),
cmd4(),
cmd5(0xaa),
];
println!("{:?}", cmds);
}
此宏是使用 incremental TT muncher to parse the commands, combined with push-down accumulation 构建最终数组的。