#位字段 # #位字段

scryer-modular-bitfield-impl

支持模块化使用枚举的位字段

1 个不稳定版本

0.11.2 2024年2月29日

#33#位字段

Download history 103/week @ 2024-03-13 220/week @ 2024-03-20 99/week @ 2024-03-27 141/week @ 2024-04-03 122/week @ 2024-04-10 80/week @ 2024-04-17 89/week @ 2024-04-24 65/week @ 2024-05-01 61/week @ 2024-05-08 79/week @ 2024-05-15 120/week @ 2024-05-22 112/week @ 2024-05-29 67/week @ 2024-06-05 75/week @ 2024-06-12 98/week @ 2024-06-19 35/week @ 2024-06-26

每月下载量287次
5 个crate中使用(通过 scryer-modular-bitfield

MIT/Apache

99KB
1.5K SLoC

Rust模块化位字段

持续集成 文档 Crates.io 代码行数
GHActions docs crates loc
  • no_std:支持不使用 std 库的嵌入式开发。
  • 该crate使用并生成100%安全的Rust代码。

描述

允许将位字段结构和枚举作为位字段指定符,其工作方式与C和C++位字段非常相似。

优点

  • 安全性:宏包含的枚举和结构体在编译时检查其有效结构。
  • 速度:生成的代码与手写的代码一样快。(见以下基准测试。)
  • 模块化:枚举可以在位字段结构体内部模块化使用。

归属

实现了David Tolnay在过程宏研讨会中引入和指定的#[bitfield]宏。

感谢David Tolnay为在这个crate中实现的宏设计了规范。

用法

使用#[bitfield]属性注释Rust结构体,以将其转换为位字段。可以使用预定义的B1B2、... B128类型作为原语来声明每个字段所需的位数。

#[bitfield]
pub struct PackedData {
    header: B4,
    body: B9,
    is_alive: B1,
    status: B2,
}

这将生成一个new构造函数以及各种getter和setter,允许以安全的方式与位字段交互

示例:构造函数

let data = PackedData::new()
    .with_header(1)
    .with_body(2)
    .with_is_alive(0)
    .with_status(3);
assert_eq!(data.header(), 1);
assert_eq!(data.body(), 2);
assert_eq!(data.is_alive(), 0);
assert_eq!(data.status(), 3);

示例:原始类型

任何实现了Specifier特质的类型都可以用作位字段字段。除了已经提到的B1、.. B128之外,还可以使用预定义的boolu8u16u32u64u128原始类型。

我们可以利用这些知识将我们的 is_alive 编码为 bool 类型,而不是 B1

#[bitfield]
pub struct PackedData {
    header: B4,
    body: B9,
    is_alive: bool,
    status: B2,
}

let mut data = PackedData::new()
    .with_is_alive(true);
assert!(data.is_alive());
data.set_is_alive(false);
assert!(!data.is_alive());

示例:枚举指定符

对于 enum 类型,我们可以非常容易地推导出 Specifier 特性,使它们也可以用作位字段类型的字段

#[derive(BitfieldSpecifier)]
pub enum Status {
    Red, Green, Yellow, None,
}

#[bitfield]
pub struct PackedData {
    header: B4,
    body: B9,
    is_alive: bool,
    status: Status,
}

示例:额外的安全保护

为了确保我们的 Status 枚举确实需要恰好 2 位,我们可以在其字段上添加 #[bits = 2]

#[bitfield]
pub struct PackedData {
    header: B4,
    body: B9,
    is_alive: bool,
    #[bits = 2]
    status: Status,
}

设置和获取我们新的 status 字段自然如下

let mut data = PackedData::new()
    .with_status(Status::Green);
assert_eq!(data.status(), Status::Green);
data.set_status(Status::Red);
assert_eq!(data.status(), Status::Red);

示例:递归位字段

我们可以使用 #[bitfield] 结构体作为其他 #[bitfield] 结构体的字段。这在多个位字段有一些公共字段时非常有用,通过向 #[bitfield] 标注的结构体的属性添加 #[derive(BitfieldSpecifier)] 来实现

#[bitfield]
#[derive(BitfieldSpecifier)]
pub struct Header {
    is_compact: bool,
    is_secure: bool,
    pre_status: Status,
}

#[bitfield]
pub struct PackedData {
    header: Header,
    body: B9,
    is_alive: bool,
    status: Status,
}

使用 #[bitfield] 宏在 Header 结构体上和 #[derive(BitfieldSpecifier)]Status 枚举上的 bits: int 参数,我们可以对最终实体的位宽有额外的编译时保证

#[derive(BitfieldSpecifier)]
#[bits = 2]
pub enum Status {
    Red, Green, Yellow
}

#[bitfield(bits = 4)]
#[derive(BitfieldSpecifier)]
pub struct Header {
    is_compact: bool,
    is_secure: bool,
    #[bits = 2]
    pre_status: Status,
}

#[bitfield(bits = 16)]
pub struct PackedData {
    #[bits = 4]
    header: Header,
    body: B9,
    is_alive: bool,
    #[bits = 2]
    status: Status,
}

示例:高级枚举指定符

对于我们的 Status 枚举,我们实际上只需要 3 个状态变体:GreenYellowRed。我们引入了 None 状态变体,因为默认情况下,Specifier 枚举需要具有一个数量是 2 的幂的变体。我们可以通过在顶部指定 #[bits = 2] 来解决这个问题,并删除我们的占位符 None 变体,同时保持它需要 2 位的不变性

# use modular_bitfield::prelude::*;

#[derive(BitfieldSpecifier)]
#[bits = 2]
pub enum Status {
    Red, Green, Yellow,
}

然而,现在拥有这样的枚举会产生位字段可能包含此类字段无效位模式的可能性。我们可以使用受保护的获取器安全地访问这些字段。为了演示,我们将使用生成的 from_bytes 构造函数,它允许我们轻松构造可能包含无效位模式的位字段

let mut data = PackedData::from_bytes([0b0000_0000, 0b1100_0000]);
//           The 2 status field bits are invalid -----^^
//           as Red = 0x00, Green = 0x01 and Yellow = 0x10
assert_eq!(data.status_or_err(), Err(InvalidBitPattern { invalid_bytes: 0b11 }));
data.set_status(Status::Green);
assert_eq!(data.status_or_err(), Ok(Status::Green));

基准测试

以下是手写代码和宏生成的代码之间的一些基准测试,这些示例获取器和设置器涵盖了相当广泛的使用案例。

我们可以得出结论,宏生成的代码与手写代码一样快。如果您看到任何改进两方面的方法,请提交一个PR。

  • cargo bench 运行基准测试
  • cargo test --benches 运行基准测试

点击此处查看所有基准测试结果。

总结

modular_bitfield 库生成的位字段与...

  • 手写代码一样高效。
  • 与替代的 bitfield 库同样高效或更高效。

展示:生成代码与手写代码对比

我们测试了以下 #[bitfield] struct

#[bitfield]
pub struct Generated {
    pub a: B9,  // Spans 2 bytes.
    pub b: B6,  // Within 2nd byte.
    pub c: B13, // Spans 3 bytes.
    pub d: B1,  // Within 4rd byte.
    pub e: B3,  // Within 4rd byte.
    pub f: B32, // Spans rest 4 bytes.
}

注意: 所有基准测试的时间结果都是10次运行的汇总。

获取器性能

get_a/generated     time:   [3.0990 ns 3.1119 ns 3.1263 ns]
get_a/handwritten   time:   [3.1072 ns 3.1189 ns 3.1318 ns]

get_b/generated     time:   [3.0859 ns 3.0993 ns 3.1140 ns]
get_b/handwritten   time:   [3.1062 ns 3.1154 ns 3.1244 ns]

get_c/generated     time:   [3.0892 ns 3.1140 ns 3.1491 ns]
get_c/handwritten   time:   [3.1031 ns 3.1144 ns 3.1266 ns]

get_d/generated     time:   [3.0937 ns 3.1055 ns 3.1182 ns]
get_d/handwritten   time:   [3.1109 ns 3.1258 ns 3.1422 ns]

get_e/generated     time:   [3.1009 ns 3.1139 ns 3.1293 ns]
get_e/handwritten   time:   [3.1217 ns 3.1366 ns 3.1534 ns]

get_f/generated     time:   [3.1064 ns 3.1164 ns 3.1269 ns]
get_f/handwritten   time:   [3.1067 ns 3.1221 ns 3.1404 ns]

设置器性能

set_a/generated     time:   [15.784 ns 15.855 ns 15.932 ns]
set_a/handwritten   time:   [15.841 ns 15.907 ns 15.980 ns]

set_b/generated     time:   [20.496 ns 20.567 ns 20.643 ns]
set_b/handwritten   time:   [20.319 ns 20.384 ns 20.454 ns]

set_c/generated     time:   [19.155 ns 19.362 ns 19.592 ns]
set_c/handwritten   time:   [19.265 ns 19.383 ns 19.523 ns]

set_d/generated     time:   [12.325 ns 12.376 ns 12.429 ns]
set_d/handwritten   time:   [12.416 ns 12.472 ns 12.541 ns]

set_e/generated     time:   [20.460 ns 20.528 ns 20.601 ns]
set_e/handwritten   time:   [20.473 ns 20.534 ns 20.601 ns]

set_f/generated     time:   [6.1466 ns 6.1769 ns 6.2127 ns]
set_f/handwritten   time:   [6.1467 ns 6.1962 ns 6.2670 ns]

许可证

许可协议为Apache License,版本2.0或MIT许可证,您可选择。

除非您明确说明,否则您提交的任何有意包含在本代码库中的贡献,根据Apache-2.0许可证定义,应如上双许可,无任何附加条款或条件。

依赖项

约1.5MB
约35K SLoC