# bobbin-bits bobbin-bits defines types representing binary numbers of width 1 to 32 and ranged values from 1 to 32. These are useful for representing small bit fields and for indexing small collections. ## Motivation Rust doesn't currently have direct support for unsigned integer types for widths other than u8, u16, u32, u64 and u128 or for ranged integers. Applications instead must store values in a larger primitive type and then check that the values stay in the correct range, typically at function boundaries. This is error prone and can impact performance. One solution is to define structs or enums to represent domain-specific values that are known to be in a specific range. This can eliminate run-time range checks at the cost of a large amount of boilerplate for managing conversions to and from these values. For some APIs the code for managing these types ends up much larger than the API itself. It can also prove to be a significant documentation challenge and barrier to learning the API. Having a unique type for almost every function parameter in an API is undesirable. This crate takes a different approach, defining a set of general-purpose types useful for representing bit fields <= 32 bits and integer ranges from 1 through 32. Conversion traits are defined to and from Rust unsigned integer types and i32, performing range checking where needed. ## Panics These types will panic if a conversion fails because the value is out of range for the destination type. ## Representation Types U1 through U6 are enums with repr(u8), allowing exhaustive matching without a default case. Their members are named with the prefix "B" followed by the n-bit binary representation of that number, like this: ``` #[repr(u8)] pub enum U3 { B000 = 0b000, B001 = 0b001, B010 = 0b010, B011 = 0b011, B100 = 0b100, B101 = 0b101, B110 = 0b110, B111 = 0b111, } ``` Similarly, R1 through R32 are enums with repr(usize). Their members are named with the prefix "X" followed by the hexadecimal represention of the number, single digits for 0-15 and two digits for 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, } ``` Types U7 and U8, U9 to U16 and U16 through U32 are wrappers around u8, u16 and u32 respectively: ``` pub struct U20(u16); ``` Unfortunately there is no literal representation of these values, so they must be constructed using `From` conversions or the `unchecked_from_xxx` functions ## Traits The following traits are currently supported for all types: - `Debug for T` - `Display for T` - `LowerHex for T` - `From for T` - `From for u8` - `From for T` - `From for u16` - `From for T` - `From for u32` - `From for T` - `From for usize` - `From for T` - `From for i32` - `PartialEq for T` The following additional traits are also supported for U1: - `From for U1` - `Not for U1` ## Examples Here's an example using the U4 bit field type: ``` use bobbin_bits::*; // Implemented using a single exhaustive match statement fn to_hex_char>(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'); ``` Using the U12 and U13 types: ``` use bobbin_bits::*; fn double_sample>(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); ``` Using the R4 range type, which supports values 0 to 3: ``` use bobbin_bits::*; // Using R4 in an exhaustive match fn get_port_name>(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>(index: I) -> u32 { // Is the optimizing compiler smart enough to eliminate the // bounds check here? PORT_ADDR[index.into() as usize] } // From 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"); ```