2个版本
使用旧Rust 2015
0.1.1 | 2018年3月17日 |
---|---|
0.1.0 | 2017年9月14日 |
#415 在 嵌入式开发
47 每月下载
在 2 crates 中使用
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");