I need to build a byte array that represents commands to a device. It may look something like this:
let cmds = [
0x01, // cmd 1
0x02, // cmd 2
0x03, 0xaa, 0xbb, // cmd 3
0x04, // cmd 4
0x05, 0xaa, // cmd 5
];
Some commands take parameters, some don't. Some parameters require calculations. Each command is fixed in size, so it's known at compile time how big the array needs to be.
It'd be nice to construct it like this, where I abstract groups of bytes into commands:
let cmds = [
cmd1(),
cmd2(),
cmd3(0, true, [3, 4]),
cmd4(),
cmd5(0xaa)
];
I haven't found any way to do this with functions or macros. I am in no_std
, so I am not using collections.
How to achieve something resembling this in Rust?
You can have each command function return an array or Vec
of bytes:
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] }
And then build your commands like so:
let cmds = [
&cmd1() as &[u8],
&cmd2(),
&cmd3(0, true, [3, 4]),
&cmd4(),
&cmd5(0xaa),
];
This builds an array of slices of bytes. To get the full stream of bytes, use 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]
You can make this more elaborate by returning some types that implement a Command
trait and collecting them into an array of trait objects, but I'll leave that up to OP.
Edit: Here's a macro that can build the array directly, using the 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);
}
If you're worried about performance, this example compiles the array into a single instruction:
movabs rax, -6195540508320529919 // equal to [0x01, 0x02, 0x03, 0xAA, 0xBB, 0x04, 0x05, 0xAA]
See it on the playground. Its limited to types that are Copy
. The length of the array must be supplied. It will panic at runtime if the array size doesn't match the combined size of the results.