Skip to content

Instantly share code, notes, and snippets.

@hannobraun
Created November 25, 2016 10:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hannobraun/ab5804e6b7a54b70997b761a02acbdfd to your computer and use it in GitHub Desktop.
Save hannobraun/ab5804e6b7a54b70997b761a02acbdfd to your computer and use it in GitHub Desktop.
Bit fields in Rust
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>());
}
}
/// 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
}
}
}
}
@JinShil
Copy link

JinShil commented Nov 26, 2016

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment