2个版本

使用旧Rust 2015

0.1.1 2018年3月17日
0.1.0 2017年9月14日

#415嵌入式开发

47 每月下载
2 crates 中使用

MIT/Apache

59KB
1.5K SLoC

bobbin-bits

bobbin-bits 定义了表示宽度为1到32的二元数和1到32的范围内值的类型。这些类型对于表示小型位域和索引小型集合非常有用。

动机

Rust 目前没有直接支持除了 u8, u16, u32, u64 和 u128 之外的未签名整数类型,也没有支持范围整数。应用程序必须将值存储在更大的原始类型中,然后在函数边界处检查值是否在正确的范围内,这容易出错,并可能影响性能。

一种解决方案是定义结构体或枚举来表示特定领域的值,这些值已知在特定范围内。这可以消除运行时范围检查,但会以大量模板代码为代价来管理这些值到和从这些值的转换。

对于某些API,管理这些类型的代码最终可能比API本身更大。这也可能成为一个重要的文档挑战,并成为学习API的障碍。在API的几乎每个函数参数中都有一个唯一类型是不理想的。

这个crate采取了一种不同的方法,定义了一组通用类型,用于表示32位及以下的位域和1到32的整数范围。定义了转换特性,用于将Rust未签名整数类型和i32转换为这些类型,并在需要时执行范围检查。

恐慌

如果转换失败,因为值超出了目标类型的范围,这些类型将引发恐慌。

表示

类型U1到U6是具有repr(u8)的枚举,允许进行穷举匹配,而不需要默认情况。它们的成员以"前缀"B后跟该数字的n位二进制表示命名,如下所示

#[repr(u8)]
pub enum U3 {
    B000 = 0b000,
    B001 = 0b001,
    B010 = 0b010,
    B011 = 0b011,
    B100 = 0b100,
    B101 = 0b101,
    B110 = 0b110,
    B111 = 0b111,
}

类似地,R1到R32是具有repr(usize)的枚举。它们的成员以"前缀"X后跟该数字的十六进制表示命名,0-15为单个数字,16-32为两位数字

#[repr(usize)]
pub enum R12 {
    X0 = 0x0,
    X1 = 0x1,
    X2 = 0x2,
    X3 = 0x3,
    X4 = 0x4,
    X5 = 0x5,
    X6 = 0x6,
    X7 = 0x7,
    X8 = 0x8,
    X9 = 0x9,
    XA = 0xa,
    XB = 0xb,
}

类型U7和U8,U9到U16和U16到U32是u8,u16和u32的包装器

pub struct U20(u16);

遗憾的是,这些值没有字面表示,因此必须使用From<T>转换或unchecked_from_xxx函数来构造

特性

以下特性目前支持所有类型

  • 调试forT
  • 显示forT
  • LowerHexforT
  • <u8> forT
  • <T> for u8
  • <u16> forT
  • <T> for u16
  • <u32> forT
  • <T> for u32
  • <usize> forT
  • <T> for usize
  • <i32> forT
  • <T> for i32
  • PartialEq<i32> forT

以下附加特性也支持U1

  • <bool> for U1
  • for U1

示例

以下是一个使用U4位字段类型的示例

use bobbin_bits::*;

// Implemented using a single exhaustive match statement
fn to_hex_char<V: Into<U4>>(v: V) -> char {
    let v = v.into();
    match v {
        U4::B0000 => '0',
        U4::B0001 => '1',
        U4::B0010 => '2',
        U4::B0011 => '3',
        U4::B0100 => '4',
        U4::B0101 => '5',
        U4::B0110 => '6',
        U4::B0111 => '7',
        U4::B1000 => '8',
        U4::B1001 => '9',
        U4::B1010 => 'a',
        U4::B1011 => 'b',
        U4::B1100 => 'c',
        U4::B1101 => 'd',
        U4::B1110 => 'e',
        U4::B1111 => 'f',
    }
}

// Call with a U4 bit field, no conversion or range checking is required.
let c = to_hex_char(U4::B1000);
assert_eq!(c, '8');

// Call with a i32, v.into() performs range check.
let c = to_hex_char(8);
assert_eq!(c, '8');

// Call with a u8, v.into() performs range check.
let c = to_hex_char(8_u8);
assert_eq!(c, '8');

// Perform range check from u32 outside of function
let v: U4 = 8u32.into();
let c = to_hex_char(v);
assert_eq!(c, '8');

// A function that will extract bits [4:7] from a u32 value
// without range checking
fn extract_u4(v: u32) -> U4 {
    unsafe {
        U4::from_u32_unchecked(v >> 4 & 0b1111)
    }
}

// No range checking needs to take place if a U4 is used
// through the computation
let c = to_hex_char(extract_u4(0b0000_0000_1000_0000));
assert_eq!(c, '8');

使用U12和U13类型

use bobbin_bits::*;

fn double_sample<V: Into<U12>>(v: V) -> U13 {
    let v = v.into();
    // Extracts into u16, multiplies, then wraps into U13
    // Performs range checking when creating the U13 value
    U13::from(v.value() * 2)
}

// Range checking takes place within double_sample()
let v = double_sample(1000);

// Unfortunately, no literal form for U13, so range checking
// happens when constructing U13 value from u16
assert_eq!(v, U13::from(2000));

// When converting from types that cannot overflow the range (such as u8),
// no range checking is needed.
assert_eq!(double_sample(100), U13::from(200u8));

// You can always access the underlying representation of the value
assert_eq!(v.value(), 2000u16);

使用支持值0到3的R4范围类型

use bobbin_bits::*;

// Using R4 in an exhaustive match
fn get_port_name<I: Into<R4>>(index: I) -> &'static str {
    let index = index.into();
    match index {
        R4::X0 => "PORTA",
        R4::X1 => "PORTB",
        R4::X2 => "PORTC",
        R4::X3 => "PORTD",
    }
}

pub const PORT_ADDR: [u32;4] = [0x1000_0000, 0x1000_2000, 0x1000_3000, 0x1000_4000];

// Using a lookup table
fn get_port_address<I: Into<R4>>(index: I) -> u32 {
    // Is the optimizing compiler smart enough to eliminate the
    // bounds check here?
    PORT_ADDR[index.into() as usize]
}

// From<i32> is implemented, range check happens in get_port_name()
let n = get_port_name(2);
assert_eq!(n, "PORTC");

// Using R4::X2 does not need a range check
let n = get_port_name(R4::X2);
assert_eq!(n, "PORTC");

无运行时依赖