You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

寻求更地道的Rust二进制文件类型解析转换实现方案

我自己实现了一套基于XML描述,把二进制解析后的&[u8]转换成Rust可用类型的方案,但总觉得太复杂。因为对Rust不够熟,暂时没找到更简洁的实现方式。下面是我试了好几次才跑通的代码:


背景

  • 读取描述二进制文件记录结构的XML文件(代码中已模拟该步骤,详见注释);
  • 每条记录包含data_type字段,存储为&str或String(取决于存储记录的Rust结构体定义);
  • 利用XML描述信息自动将二进制文件解析得到的&[u8]转换为Rust可用类型(代码中模拟了二进制数据,解析二进制文件并非重点)。

简化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 用于将XML字符串解析为Rust结构体

#[derive(Serialize, Deserialize, Debug)]
pub struct Records {
    pub records: Vec<Record>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct Record {
    ...,
    pub data_type: &str, // 也可以是String,本示例未使用该结构体
    ...,
}

// 根据每条记录的data_type将数据转换为Rust可用类型

工作代码

第一个宏

用于创建将&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())
        }
    };
}

第二个宏

用于创建从DataTypes枚举变体中提取特定值的impl块,示例输出见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字节
    IEEE754LSBSingle(f32),
    // 8字节
    IEEE754LSBDouble(f64),
}

示例一

匹配data_type: &str描述,生成对应的转换函数和提取impl块供后续使用:

fn first_example(){
    // 模拟从二进制文件解析得到的数据
    let mut data: &[u8] = &[172, 152, 111, 195];
    let mut data2: &[u8] = &[172, 152, 111, 195, 117, 93, 133, 192];
    
    // 模拟遍历不同类型的记录
    for dtype in ["IEEE754LSBSingle", "IEEE754LSBDouble"] {
        match dtype {
            "IEEE754LSBSingle" => {
                create_unwrap_impl_for_type!(unwrap_le_f32,DataTypes::IEEE754LSBSingle,f32);
                /* 
                生成代码:
                    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);
                /* 
                生成代码:
                    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 Example\tIEEE754LSBSingle {:?}",single);    
            },
            "IEEE754LSBDouble" => {
                create_unwrap_impl_for_type!(unwrap_le_f64,DataTypes::IEEE754LSBDouble,f64);
                /* 
                生成代码:
                    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);
                /* 
                生成代码:
                    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 Example\tIEEE754LSBDouble {:?}",double);
                },
                _ => panic!(),
        };
    }
}

统一宏

整合上述两个宏,用于生成转换函数和提取impl块,示例二使用该宏:

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(){
    // 模拟从二进制文件解析得到的数据
    let mut data: &[u8] = &[172, 152, 111, 195];
    let mut data2: &[u8] = &[172, 152, 111, 195, 117, 93, 133, 192];
    
    // 模拟遍历不同类型的记录
    for dtype in ["IEEE754LSBSingle", "IEEE754LSBDouble"] {
        match dtype {
            "IEEE754LSBSingle" => {
                // 生成代码与示例一一致
                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 Example\tIEEE754LSBSingle {:?}",single);    
            },
            "IEEE754LSBDouble" => {
            // 生成代码与示例一一致
                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 Example\tIEEE754LSBDouble {:?}",double);
                },
                _ => panic!(),
        };
    }
}

主函数

fn main() {
    first_example();
    second_example();
}

内容的提问来源于stack exchange,提问作者William

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 17:50:29