Search Results


Bitfields Demystified

Apr 07, 2021

Contents

Bitfields are mysterious when it comes to Rust language and people don't know the right way to approach them. The 101 series of this journal is to present things that I find everyone should know about that particular language. So, let's start with our favorite Rust ❤️.

What is Bitfield?

Bitfields are simple data structures, which is used to define standard properties or to enable features inside a function or a program. Typically, they are used at a lower level of the machine.

For example, the microcontroller inside an Arduino (idk if you are familiar with it) uses very low memory and store space. So, we need to design a particular set of options or states that are to be enabled once that bit is on.

Here we will consider a 8-bit solution which can be defined using memory space of only a single byte and assignable via u8 type in Rust which is represented as 1111 1111

Let's arbitrarily take 8 options that we want for a program we need to develop for a factory robotic arm, suppose:

  1. Bit 1: Toggles power to arm
  2. Bit 2: Grabs the current item from assembly line
  3. Bit 3: Skip to next item
  4. Bit 4: Back to previous item
  5. Bit 5: Put the item on rejected line
  6. Bit 6: Put the item on assembly line
  7. Bit 7: Flips the item upside down (reverses it)
  8. Bit 8: Shakes the item

Now, we have 8 options that we want to consider for a particular operation:

Operation 1: Grabs the current item and rejects it

We need 3 bits enabled in this case, Bit 1 OR Bit 2 OR Bit 5. Doing this, our bitmap will look something like 0001 0011

Operation 2: Grabs the current item, shakes it, flips it and put it back

We need 5 bits enabled for this operation, Bit 1 OR Bit 2 OR Bit 6 OR Bit 7 OR Bit 8. In this case, our bitmap will be like this 1110 0011

Usage of Bitfields in Rust

There are 2 ways of programming bitfields in Rust

  1. Using structures
  2. Using enumerators

Using Structures

Bitfields used in structures are based on concept of associated constants, which uses direct association instead of type conversion of different variants. This calls for an easy comparison of bitfields without type casting it everytime.

pub struct RoboticArmProperties;

impl RoboticArmProperties {
    pub const POWER: u8 = 1 << 0;
    pub const GRAB_ASSEMBLY: u8 = 1 << 1;
    pub const SKIP_TO_NEXT: u8 = 1 << 2;
    pub const BACK_TO_PREV: u8 = 1 << 3;
    pub const PUT_REJECTED: u8 = 1 << 4;
    pub const PUT_ASSEMBLY: u8 = 1 << 5;
    pub const VERTICAL_FLIP: u8 = 1 << 6;
    pub const SHAKES: u8 = 1 << 7;
}

fn main() {
    let operation_1 = RoboticArmProperties::POWER
        | RoboticArmProperties::GRAB_ASSEMBLY
        | RoboticArmProperties::PUT_REJECTED;
    let operation_2 = RoboticArmProperties::POWER
        | RoboticArmProperties::GRAB_ASSEMBLY
        | RoboticArmProperties::PUT_ASSEMBLY
        | RoboticArmProperties::VERTICAL_FLIP
        | RoboticArmProperties::SHAKES;
    println!("Enabling Operation 1: {}", operation_1);
    println!("Enabling Operation 2: {}", operation_2);
}

Using Enumerators

We will use enumerators to create new properties and adding them together as a single typeform using #[repr(u8)] macro which declares the bits to u8 type.

Furthermore, to use these bitfields, we have to again typecast the enum variants to properly get our assigned values.

#[repr(u8)]
pub enum RoboticArmProperties {
    Power = 1 << 0,
    GrabAssembly = 1 << 1,
    SkipToNext = 1 << 2,
    BackToPrev = 1 << 3,
    PutRejected = 1 << 4,
    PutAssembly = 1 << 5,
    VerticalFlip = 1 << 6,
    Shakes = 1 << 7,
}

fn main() {
    let operation_1 = RoboticArmProperties::Power as u8
        | RoboticArmProperties::GrabAssembly as u8
        | RoboticArmProperties::PutRejected as u8;
    let operation_2 = RoboticArmProperties::Power as u8
        | RoboticArmProperties::GrabAssembly as u8
        | RoboticArmProperties::PutAssembly as u8
        | RoboticArmProperties::VerticalFlip as u8
        | RoboticArmProperties::Shakes as u8;
    println!("Enabling Operation 1: {}", operation_1);
    println!("Enabling Operation 2: {}", operation_2);
}