bitdecl

## Introduction

In pursuit of wanting to make my projects more ‘independent’ (less external dependencies), I decided to implement the features these 3 crates provide in one: bitfield, bit-field and bitflags. I also tried to make most of this const. When const-traits stabilize it should help reduce the amount of code required and make it more consistent.

## bitfield

bitfield provides the bitfield! macro used to generate bitfield-like structs. My implementation uses a slightly different syntax, makes no use of traits, and works exclusively via declarative macros. The macros ensure type safety and correct usage at compile time with (hopefully) useful errors.

For example:

use bitdecl::bitfield;

bitfield! {
    /// example bitfield backed by a u64 with access to low and high bits
    #[derive(Debug)]
    pub struct Integer(u64);
    /// low bits of the u64
    low : [u32 @ 31: 0],
    /// high bits of the u64
    pub high: [u32 @ 63:32]
}

let mut int = Integer(0);
// the `low` field is private, so it is automatically prefixed with an `_`.
int._set_low(u32::MAX);
int.set_high(u32::MAX);
bitfield! {
    (attributes)* vis? struct Name(StorageTy);
    (vis? field : [FieldTy @ lsb | msb:lsb]),*
}
Syntax explanation… (expand)
  • vis? is an optional visibility modifier (pub only currently)
  • (attributes)* means zero or more outer attributes (#[derive(Hash)], doc comments etc…)
  • StorageTy and FieldTy are types (u128, u64, …), FieldTy cannot be bigger than StorageTy.
  • FieldTy can be a bool if the bitrange is the size of a bit.
  • Name and FlagName are idents, used as struct/constant names.
  • msb/lsb are integer bit indices:
    • @ lsb selects a single bit
    • @ msb:lsb selects an inclusive range (with msb >= lsb)

Items are comma-separated and the last item must not have a trailing comma

More on this macro’s usage and syntax is detailed in the docs.

## bit-field

bit-field provides BitArray and BitField traits, that can be used to extract and manipulate bits in primitives by default (and whatever you implement them for). My crate currently only supports primitive types, since I do not use traits. I provide get_bits! and set_bits! which are guaranteed to evaluate at compile time if the input value is a literal, otherwise it depends on usage context.

For example:

use bitdecl::{BitRangeInclusive, bitrange, get_bits};
 
let mut bits: u64 = 2;

// gets bit 1
assert!(0b00000010u8 == get_bits!(bits, 1));
// using intel syntax bitrange
assert!(0b00000010u8 == get_bits!(bits, 1:0));

set_bits!(bits, 1:0, 0);
 
// verify it unset the bits, with a inclusive bitrange syntax
assert!(0b00000000u8 == get_bits!(bits, 0..=2));

The syntax rules for get_bits! and set_bits!:

get_bits!(
    value, [lsb | msb:lsb | lsb..=msb | WITH RANGE expr]
)

set_bits!(
    value, new_value, [lsb | msb:lsb | lsb..=msb | WITH RANGE expr]
)
Syntax explanation… (expand)
  • |seperates the possible syntax options
  • msb/lsb are integer bit indices:
    • msb:lsb is an inclusive range (with msb >= lsb)
  • expr is anything that evaluates to a BitRangeInclusive

Items are comma-separated and the last item must not have a trailing comma

See the documentation on get_bits! and set_bits! for more details.

## bitflags

bitflags provides the bitflags! macro used to generate types for C-style flags with ergonomic APIs. My implementation is very minimalistic with small syntax changes and only set, contains and bits as methods.

For example:

use bitdecl::bitflags;
 
bitflags! {
    /// example bitflags backed by a u64
    #[derive(Debug)]
    pub struct ExampleFlags: u64 {
        /// we can also use doc comments
        const TEST @ 1:0;
        const MEOW @   2;
    }
}
 
let mut flags = ExampleFlags(0);
flags.set(ExampleFlags::TEST, true);
 
// check if it correctly set the bits
assert!(flags.bits() == ExampleFlags::TEST.bits());
// check if `contains` functions correctly
assert!(flags.contains(ExampleFlags::TEST));
// debug print it, will fail to compile if the derive did not apply.
println!("{:?}", flags);

The syntax is as follows:

bitflags! {
    (attributes)* vis? struct Name: StorageTy {
        ((attributes)* FlagName @ lsb | msb:lsb),*
    }
}
Syntax explanation… (expand)
  • vis? is an optional visibility modifier (pub, pub(crate), …)
  • (attributes)* means zero or more outer attributes (#[derive(Hash)], doc comments etc…)
  • StorageTy represents a type (u128, u64, …).
  • Name and FlagName are idents, used as struct/constant names.
  • msb/lsb are integer bit indices:
    • @ lsb selects a single bit
    • @ msb:lsb selects an inclusive range (with msb >= lsb)

Items are comma-separated and the last item must not have a trailing comma

More on this macro’s usage and its syntax is detailed in the docs.