You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在Rust宏系统中求值表达式?并实现位信息结构体生成宏

实现Rust宏生成带位字段的无符号整数结构体

刚好我之前做过类似的二进制协议处理,这个需求非常典型。我们可以通过自定义宏来自动生成结构体,以及对应的位字段操作方法,完全匹配你想要的效果。

宏的实现代码

首先直接上完整的宏定义,之后我会一步步解释每个部分的作用:

#[macro_export]
macro_rules! bitmessage {
    (struct $name:ident($ty:ty); $($field:ident: $bits:expr, $default:expr;)*) => {
        #[derive(Debug, Clone, Copy, PartialEq, Eq)]
        pub struct $name {
            inner: $ty,
        }

        impl $name {
            /// 创建带有默认值的实例
            pub fn new() -> Self {
                let mut inner = 0;
                // 记录已使用的位长度,用于计算偏移
                let mut used_bits = 0;
                $(
                    // 将默认值放到对应的位位置(从高位到低位排列)
                    let offset = std::mem::size_of::<$ty>() * 8 - $bits - used_bits;
                    inner |= ($default as $ty) << offset;
                    used_bits += $bits;
                )*
                // 检查总位长度是否超过底层类型的位数
                assert!(used_bits <= std::mem::size_of::<$ty>() * 8, "Total bits exceed type capacity");
                Self { inner }
            }

            /// 从字节数组构造实例(大端序,可修改为小端序)
            pub fn from_bytes(bytes: &[u8]) -> Result<Self, &'static str> {
                if bytes.len() != std::mem::size_of::<$ty>() {
                    return Err("Byte length doesn't match the underlying type");
                }
                let inner = match bytes.try_into() {
                    Ok(arr) => <$ty>::from_be_bytes(arr),
                    Err(_) => return Err("Failed to convert slice to array"),
                };
                Ok(Self { inner })
            }

            /// 获取底层原始整数
            pub fn inner(&self) -> $ty {
                self.inner
            }

            /// 生成每个字段的getter方法
            $(
                pub fn $field(&self) -> $ty {
                    let mask = (1 << $bits) - 1;
                    let mut used_bits = 0;
                    $(
                        used_bits += $bits;
                    )*
                    used_bits -= $bits;
                    let offset = std::mem::size_of::<$ty>() * 8 - $bits - used_bits;
                    (self.inner >> offset) & mask
                }
            )*

            /// 生成每个字段的setter方法
            $(
                pub fn set_$field(&mut self, value: $ty) {
                    let mask = (1 << $bits) - 1;
                    if value > mask {
                        panic!("Value {} is too large for a {} bit field", value, $bits);
                    }
                    let mut used_bits = 0;
                    $(
                        used_bits += $bits;
                    )*
                    used_bits -= $bits;
                    let offset = std::mem::size_of::<$ty>() * 8 - $bits - used_bits;
                    // 先清除对应位,再设置新值
                    self.inner &= !(mask << offset);
                    self.inner |= (value & mask) << offset;
                }
            )*

            /// 转换为字节数组(大端序)
            pub fn to_bytes(&self) -> [u8; std::mem::size_of::<$ty>()] {
                self.inner.to_be_bytes()
            }
        }

        impl Default for $name {
            fn default() -> Self {
                Self::new()
            }
        }
    };
}

宏的工作原理

我调整了一些细节,让它更健壮:

  1. 语法解析:宏会识别你定义的结构体名(比如Header)、底层无符号类型(比如u16),以及每个字段的名称、位长度、默认值。
  2. 结构体生成:生成的结构体包含一个inner字段,用来存储底层的无符号整数,所有位字段都存在这里面。
  3. 默认实例创建new()方法会自动把每个字段的默认值放到对应的位位置,并且会检查总位长度是否超过底层类型的容量(比如u16最多16位,两个8位字段刚好)。
  4. 字节转换from_bytesto_bytes方法用大端序处理字节(如果需要小端序,把from_be_bytesto_be_bytes换成from_le_bytesto_le_bytes就行)。
  5. 字段操作:每个字段自动生成getter(比如version())和setter(比如set_version()),setter还会检查值是否超出字段的位长度范围,防止溢出。

使用示例

完全按照你给出的语法来使用:

// 用宏生成Header结构体
bitmessage! {
    struct Header(u16);
    version: 8, 5;
    data: 8, 5;
}

fn main() {
    // 创建默认实例
    let mut header = Header::default();
    println!("默认Header: {:?}", header);
    println!("默认version: {}", header.version()); // 输出5
    println!("默认data: {}", header.data()); // 输出5

    // 修改字段值
    header.set_version(10);
    header.set_data(20);
    println!("修改后version: {}", header.version()); // 输出10
    println!("修改后data: {}", header.data()); // 输出20

    // 从字节数组构造(大端序,0x0A对应version=10,0x14对应data=20)
    let bytes = [0x0A, 0x14];
    let header_from_bytes = Header::from_bytes(&bytes).unwrap();
    println!("从字节构造的version: {}", header_from_bytes.version()); // 输出10
    println!("从字节构造的data: {}", header_from_bytes.data()); // 输出20

    // 转换回字节数组
    println!("转换为字节: {:?}", header.to_bytes()); // 输出[10, 20]
}

可扩展的点

如果需要更灵活的功能,可以做这些调整:

  • 支持小端序:修改from_be_bytesfrom_le_bytes即可。
  • 自定义位顺序:如果希望字段从低位到高位排列,只需要修改偏移量的计算方式(从0开始累加位长度)。
  • 错误处理替代panic:把setter的panic改成返回Result,让调用者自己处理错误。

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

火山引擎 最新活动