Created
November 25, 2016 10:39
-
-
Save hannobraun/ab5804e6b7a54b70997b761a02acbdfd to your computer and use it in GitHub Desktop.
Bit fields in Rust
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use core::marker::PhantomData; | |
#[derive(Clone, Copy, Debug)] | |
pub struct BitField<T>(u32, PhantomData<T>); | |
impl<T> BitField<T> { | |
#[cfg(test)] | |
pub fn new(bit_field: u32) -> Self { | |
BitField(bit_field, PhantomData) | |
} | |
pub fn get<M>(&self) -> Option<M::Value> | |
where M: Member<BitField=T, Read=R> | |
{ | |
let mask = M::mask(); | |
let shift = M::shift(); | |
M::Value::from_u32((self.0 & mask) >> shift) | |
} | |
pub fn set<M>(self, value: M::Value) -> Self | |
where M: Member<BitField=T, Write=W> | |
{ | |
let mask = M::mask(); | |
let shift = M::shift(); | |
let bit_field = self.0 & !mask; | |
let value = (value.to_u32() << shift) & mask; | |
BitField(bit_field | value, PhantomData) | |
} | |
} | |
#[cfg(test)] | |
impl<T> Default for BitField<T> { | |
fn default() -> Self { | |
BitField::new(0) | |
} | |
} | |
pub struct R; | |
pub struct W; | |
pub trait Member { | |
type BitField; | |
type Value: Value; | |
type Read; | |
type Write; | |
// This should be an associated constants, but those are not stable yet. | |
fn mask() -> u32; | |
/// Provides a default implementation that computes the shift from the mask | |
/// | |
/// Since this computation only uses simple operations on values known at | |
/// compile-time, a sufficiently smart compiler should be able to optimize | |
/// it out completely. And indeed, this is what seems to be happening. | |
/// | |
/// In case this optimization doesn't work for you, you can override this | |
/// method and return the shift value manually. | |
fn shift() -> u32 { | |
let mut mask = Self::mask(); | |
let mut shift = 0; | |
while mask & 0b1 == 0 { | |
mask >>= 1; | |
shift += 1; | |
if shift >= 31 { | |
return 0; | |
} | |
} | |
shift | |
} | |
} | |
pub trait Value : Sized { | |
fn from_u32(value: u32) -> Option<Self>; | |
fn to_u32(self) -> u32; | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::BitField; | |
#[derive(Clone, Copy)] | |
pub struct MyBitField; | |
member!(rw MyMember for MyBitField: 0b00111100, MyValue); | |
pub struct MyMember; | |
bit_field_value!( | |
#[derive(Debug)] | |
value MyValue { | |
Value1010 = 0b1010, | |
} | |
); | |
#[test] | |
fn it_should_read_a_value() { | |
let bit_field = BitField::new(0b01101001); | |
assert_eq!(Some(MyValue::Value1010), bit_field.get::<MyMember>()); | |
} | |
#[test] | |
fn it_should_write_a_value() { | |
let bit_field = BitField::new(0b01010101); | |
let bit_field = bit_field.set::<MyMember>(MyValue::Value1010); | |
assert_eq!(Some(MyValue::Value1010), bit_field.get::<MyMember>()); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// Defines a bit field member | |
/// Designed to be used within the `bit_field!` macro. | |
macro_rules! member { | |
(rw $member:tt for $bit_field:ty: $mask:expr, $value:ty) => { | |
impl $crate::bit_field::Member for $member { | |
type BitField = $bit_field; | |
type Value = $value; | |
type Read = $crate::bit_field::R; | |
type Write = $crate::bit_field::W; | |
fn mask() -> u32 { $mask } | |
} | |
}; | |
(ro $member:tt for $bit_field:ty: $mask:expr, $value:ty) => { | |
impl $crate::bit_field::Member for $member { | |
type BitField = $bit_field; | |
type Value = $value; | |
type Read = $crate::bit_field::R; | |
type Write = (); | |
fn mask() -> u32 { $mask } | |
} | |
}; | |
(wo $member:tt for $bit_field:ty: $mask:expr, $value:ty) => { | |
impl $crate::bit_field::Member for $member { | |
type BitField = $bit_field; | |
type Value = $value; | |
type Read = (); | |
type Write = $crate::bit_field::W; | |
fn mask() -> u32 { $mask } | |
} | |
}; | |
} | |
/// Implements an enum designed to be used as a value in the `bit_field` module | |
macro_rules! bit_field_value { | |
{ | |
$(#[$attr:meta])* | |
value $name:ident { | |
$($variant_name:ident = $variant_value:expr,)* | |
} | |
} => { | |
#[derive(Eq, PartialEq)] | |
$(#[$attr])* | |
pub enum $name { | |
$($variant_name = $variant_value,)* | |
} | |
impl $crate::bit_field::Value for $name { | |
fn from_u32(value: u32) -> Option<Self> { | |
match value { | |
$($variant_value => Some($name::$variant_name),)* | |
_ => None, | |
} | |
} | |
fn to_u32(self) -> u32 { | |
self as u32 | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I'm looking forward to formal documentation; I'm a little new to Rust, so I'm having a little bit of trouble fully understanding what's going on here. Thanks for sharing, though. Here's my take on a similar thing: https://gist.github.com/JinShil/c0fdd4afde144f3697982fd04cbb2705