我认为我的解决方案比它应该的更复杂,但我对Rust还不够熟悉,不知道更好的方法。以下是我经过多次尝试和错误后得到的唯一解决方案。
Rust Playground
上下文
- 从描述二进制文件的记录结构的XML文件中读取。在下面的工作代码中跳过了这一步骤,并对其进行了简单的模拟。请参阅代码块中的注释
- 每个记录提供的字段之一是CCD_ 1描述,其被存储为&str或String,具体取决于保存记录的Rust结构体的定义方式
- 使用XML描述信息从二进制文件中读取记录,以自动化所得到的解析&将[u8]强制转换为,以便以后可以使用。由于解析二进制文件不是重点,因此在下面的代码中也模拟了这些数据
简化的XML描述文件示例
假设更多的记录和更多的数据类型。这不是工作代码中使用的。
use quick_xml;
let xml_desc = r#""
<Records>
<RecordDesc>
<name>Single</name>
<number>1</number>
<location unit="byte">0</location>
<data_type>IEEE754LSBSingle</data_type>
<length> unit="byte">4</length>
</RecordDesc>
<RecordDesc>
<name>Double</name>
<number>1</number>
<location unit="byte">5</location>
<data_type>IEEE754LSBDouble</data_type>
<length> unit="byte">8</length>
</RecordDesc>
</Records>
""#;
// quick_xml/serde used to get that string of xml into the rust records
#[derive(Serialize, Deserialize, Debug)]
pub struct Records {
pub records: Vec<Record>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Record {
...,
pub data_type: &str, //could also be String, but this example doesn't use this struct
...,
}
// For each record use data_type to cast into type rust can use
工作代码
第一个宏
创建将&mut&[u8]转换为特定的Rust等效类型。示例输出如first_example
所示。
macro_rules! type_cast_function {
($func_name:ident, $endian:ident, $output_type:ty ) => {
fn $func_name(input: &mut &[u8]) -> $output_type {
let (int_bytes, _) = input.split_at(std::mem::size_of::<$output_type>());
<$output_type>::$endian(int_bytes.try_into().unwrap())
}
};
}
第二个宏
创建impl块,用于从DataTypes中的变量中解开每个特定值。示例输出如first_example
所示。
macro_rules! create_unwrap_impl_for_type {
($unwrap_name:ident, $variant:path, $output_type:ty) => {
impl DataTypes {
pub fn $unwrap_name(self) -> $output_type {
match self {
$variant(val) => val,
_ => panic!(),
}
}
}
};
}
为各种数据类型创建枚举
注:案例反映了xml_desc
中的案例
#[derive(Debug)]
pub enum DataTypes {
// 4 Bytes
IEEE754LSBSingle(f32),
// 8 Bytes
IEEE754LSBDouble(f64),
}
第一个示例
匹配data_type: &str
描述,并生成相关函数和impl块,用于展开要在其他地方使用的每个匹配的值。
fn first_example(){
// Simulated Data that would come from parsing the binary file
let mut data: &[u8] = &[172, 152, 111, 195];
let mut data2: &[u8] = &[172, 152, 111, 195, 117, 93, 133, 192];
// Simulated looping through records with different types
for dtype in ["IEEE754LSBSingle", "IEEE754LSBDouble"] {
match dtype {
"IEEE754LSBSingle" => {
create_unwrap_impl_for_type!(unwrap_le_f32,DataTypes::IEEE754LSBSingle,f32);
/*
outputs:
impl DataTypes {
pub fn unwrap_le_f32(self) -> f32 {
match self {
DataTypes::IEEE754LSBSingle(val) => val,
_ => panic!(),
}
}
}
*/
type_cast_function!(read_le_f32, from_le_bytes, f32);
/*
outputs:
fn read_le_f32(input: &mut &[u8]) -> f32 {
let (int_bytes, _) = input.split_at(std::mem::size_of::<f32>());
f32::from_le_bytes(int_bytes.try_into().unwrap())
}
*/
let single = DataTypes::IEEE754LSBSingle(read_le_f32(&mut data)).unwrap_le_f32();
println!("First ExampletIEEE754LSBSingle {:?}",single);
},
"IEEE754LSBDouble" => {
create_unwrap_impl_for_type!(unwrap_le_f64,DataTypes::IEEE754LSBDouble,f64);
/*
outputs:
impl DataTypes {
pub fn unwrap_le_f64(self) -> f64 {
match self {
DataTypes::IEEE754LSBDouble(val) => val,
_ => panic!(),
}
}
}
*/
type_cast_function!(read_le_f64, from_le_bytes, f64);
/*
outputs:
fn read_le_f64(input: &mut &[u8]) -> f64 {
let (int_bytes, _) = input.split_at(std::mem::size_of::<f64>());
f64::from_le_bytes(int_bytes.try_into().unwrap())
}
*/
let double = DataTypes::IEEE754LSBDouble(read_le_f64(&mut data2)).unwrap_le_f64();
println!("First ExampletIEEE754LSBDouble {:?}",double);
},
_ => panic!(),
};
}
}
一个宏来管理所有宏
一个宏用于从其他宏创建函数和impl块。使上面的first_example
和下面的second_example
之间存在差异
macro_rules! generate_casting_extraction_functions {
($func_name:ident, $endian:ident, $unwrap_name:ident, $variant:path, $output_type:ty) => {
create_unwrap_impl_for_type!($unwrap_name, $variant, $output_type);
type_cast_function!($func_name, $endian, $output_type);
}
}
第二个示例
匹配data_type: &str
描述,并生成相关函数和impl块,用于展开要在其他地方使用的每个匹配的值。
fn second_example(){
// Simulated Data that would come from parsing the binary file
let mut data: &[u8] = &[172, 152, 111, 195];
let mut data2: &[u8] = &[172, 152, 111, 195, 117, 93, 133, 192];
// Simulated looping through records with different types
for dtype in ["IEEE754LSBSingle", "IEEE754LSBDouble"] {
match dtype {
"IEEE754LSBSingle" => {
// Same output as first_example
generate_casting_extraction_functions!(read_le_f32_2, from_le_bytes,unwrap_le_f32_2,DataTypes::IEEE754LSBSingle,f32);
let single = DataTypes::IEEE754LSBSingle(read_le_f32_2(&mut data)).unwrap_le_f32_2();
println!("Second ExampletIEEE754LSBSingle {:?}",single);
},
"IEEE754LSBDouble" => {
// Same output as first_example
generate_casting_extraction_functions!(read_le_f64_2, from_le_bytes,unwrap_le_f64_2,DataTypes::IEEE754LSBDouble,f64);
let double = DataTypes::IEEE754LSBDouble(read_le_f64_2(&mut data2)).unwrap_le_f64_2();
println!("Second ExampletIEEE754LSBDouble {:?}",double);
},
_ => panic!(),
};
}
}
fn main() {
first_example();
second_example();
}
下面是我要做的。
首先,您需要两个枚举:
- 一个只有单元变体的字符串,它只能更好地表示
data_type
字符串,我将其称为data_type
0
#[derive(Clone, Copy, Debug)]
enum DataKind {
// 4 bytes
IEEE754LSBSingle,
// 8 bytes
IEEE754LSBDouble,
...etc
}
- 和一个将保存您从二进制文件解析的数据的文件
#[derive(Debug)]
enum DataTypes {
// 4 bytes
IEEE754LSBSingle(f32),
// 8 bytes
IEEE754LSBDouble(f64),
...etc
}
然后,您需要一个函数来解释目标类型所需的输入字节数,并将结果存储在相应的DataTypes
值中:
impl DataKind {
fn parse(self, input: &mut &[u8]) -> DataTypes {
match self {
DataKind::IEEE754LSBSingle => DataTypes::IEEE754LSBSingle({
let (bytes, _) = input.split_at(std::mem::size_of::<f32>());
f32::from_le_bytes(bytes.try_into().unwrap())
}),
DataKind::IEEE754LSBDouble => DataTypes::IEEE754LSBDouble({
let (bytes, _) = input.split_at(std::mem::size_of::<f64>());
f64::from_le_bytes(bytes.try_into().unwrap())
}),
...etc
}
}
}
值得庆幸的是,使用宏可以很容易地同时生成所有这些:
macro_rules! generate_datatypes_parsing {
[$( $name:ident($target_type:ty => $conversion:ident) ),+ $(,)*] => {
#[derive(Clone, Copy, Debug)]
pub enum DataKind {
$( $name, )*
}
#[derive(Debug)]
pub enum DataTypes {
$( $name($target_type), )*
}
impl DataKind {
fn parse(self, input: &mut &[u8]) -> DataTypes {
match self {
$(
DataKind::$name => DataTypes::$name({
let (bytes, _) = input.split_at(
std::mem::size_of::<$target_type>()
);
<$target_type>::$conversion(bytes.try_into().unwrap())
}),
)*
}
}
}
};
}
generate_datatypes_parsing![
IEEE754LSBSingle(f32 => from_le_bytes),
IEEE754LSBDouble(f64 => from_le_bytes),
...etc
];
然后你可以像这样使用DataKind::parse
:
fn main() {
// Simulated Data that would come from parsing the binary file
let mut data: &[u8] = &[172, 152, 111, 195];
let mut data2: &[u8] = &[172, 152, 111, 195, 117, 93, 133, 192];
// parsing will eventually go in a loop somewhere
println!("First Examplet{:?}", DataKind::IEEE754LSBSingle.parse(&mut data));
println!("First Examplet{:?}", DataKind::IEEE754LSBDouble.parse(&mut data2));
}
操场
为什么选择DataKind
最好使用像DataKind
这样的枚举,因为这样可以从编译器获得更多的保证。传递一个没有生存期的Copy
枚举也比传递一个有一些生存期需要担心的&str
要容易得多。
当然,您应该为DataKind
使用#[derive(Deserialize)]
,这样serde就可以为您从&str
进行转换。
返回Result
您可能希望从fn parse
返回一个具有自定义错误类型的Result
。如果您这样做,我建议您使用自定义的split_at
函数,如果超出范围,该函数也会返回Result
。