pub trait BitField {
    fn load_le<I>(&self) -> I
    where
        I: Integral
; fn load_be<I>(&self) -> I
    where
        I: Integral
; fn store_le<I>(&mut self, value: I)
    where
        I: Integral
; fn store_be<I>(&mut self, value: I)
    where
        I: Integral
; fn load<I>(&self) -> I
    where
        I: Integral
, { ... } fn store<I>(&mut self, value: I)
    where
        I: Integral
, { ... } }
Expand description

C-Style Bit-Field Access

This trait describes data transfer between a BitSlice region and an ordinary integer. It is not intended for use by any other types than the data structures in this crate.

The methods in this trait always operate on the bitslice.len() least significant bits of an integer, and ignore any remaining high bits. When loading, any excess high bits not copied out of a bit-slice are cleared to zero.

Usage

The trait methods all panic if called on a bit-slice that is wider than the integer type being transferred. As such, the first step is generally to subslice a larger data structure into exactly the region used for storage, with bits[start .. end]. Then, call the desired method on the narrowed bit-slice.

Target-Specific Behavior

If you do not care about the details of the memory layout of stored values, you can use the .load() and .store() unadorned methods. These each forward to their _le variant on little-endian targets, and their _be variant on big-endian. These will provide a reasonable default behavior, but do not guarantee a stable memory layout, and their buffers are not suitable for de/serialization.

If you require a stable memory layout, you will need to choose a BitSlice with a fixed O: BitOrder type parameter (not LocalBits), and use a fixed method suffix (_le or _be). You should probably also use u8 as your T: BitStore parameter, in order to avoid any byte-ordering issues. bitvec never interferes with processor concepts of wide-integer layout, and always relies on the target machine’s behavior for this work.

Element- and Bit- Ordering Combinations

Remember: the _le and _be method suffixes are completely independent of the Lsb0 and Msb0 types! _le and _be refer to the order in which successive memory elements are considered to gain numerical significance, while BitOrder refers only to the order of successive bits in one memory element.

The BitField and BitOrder traits are not related.

When a load or store operation is contained in only one memory element, then the _le and _be methods have the same behavior: they exchange an integer value with the segment of the element that its governing BitSlice considers live. Only when a BitSlice covers multiple elements does the distinction come into play.

The _le methods consider numerical significance to start low and increase with increasing memory address, while the _be methods consider numerical significance to start high and decrease with increasing memory address. This distinction affects the order in which memory elements are used to load or store segments of the exchanged integer value.

Each trait method has detailed visual diagrams in its documentation. Additionally, each implementation’s documentation has diagrams that show what the governed bit-sections of elements are! Be sure to check each, or to run the demonstration with cargo run --example bitfield.

Bitfield Value Types

When interacting with a bit-slice as a C-style bitfield, it can only store the signed or unsigned integer types. No other type is permitted, as the implementation relies on the 2’s-complement significance behavior of processor integers. Record types and floating-point numbers do not have this property, and thus have no sensible default protocol for truncation and un/marshalling that bitvec can use.

If you have such a protocol, you may implement it yourself by providing a de/serialization transform between your type and the integers. For instance, a numerically-correct protocol to store floating-point numbers in bitfields might look like this:

use bitvec::mem::bits_of;
use funty::Floating;

fn to_storage<F>(num: F, width: usize) -> F::Raw
where F: Floating {
  num.to_bits() >> (bits_of::<F>() - width)
}

fn from_storage<F>(val: F::Raw, width: usize) -> F
where F: Floating {
  F::from_bits(val << (bits_of::<F>() - width))
}

This implements truncation in the least-significant bits, where floating-point numbers store disposable bits in the mantissa, rather than in the most-significant bits which contain the sign, exponent, and most significant portion of the mantissa.

Required Methods

Little-Endian Integer Loading

This method loads an integer value from a bit-slice, using little-endian significance ordering when the bit-slice spans more than one T element in memory.

Little-endian significance ordering means that if a bit-slice occupies an array [A, B, C], then the bits stored in A are considered to contain the least significant segment of the loaded integer, then B contains the middle segment, and then C contains the most significant segment.

The segments are combined in order, that is, as the raw bit-pattern 0b<padding><C><B><A>. If the destination type is signed, the loaded value is sign-extended according to the most-significant bit in the C segment.

It is important to note that the O: BitOrder parameter of the bit-slice from which the value is loaded does not affect the bit-pattern of the stored segments. They are always stored exactly as they exist in an ordinary integer. The ordering parameter only affects which bits in an element are available for storage.

Type Parameters
  • I: The integer type being loaded. This can be any of the signed or unsigned integers.
Parameters
  • &self: A bit-slice region whose length is in the range 1 ..= I::BITS.
Returns

The contents of the bit-slice, interpreted as an integer.

Panics

This panics if self.len() is 0, or greater than I::BITS.

Examples

Let us consider an i32 value stored in 24 bits of a BitSlice<u8, Msb0>:

use bitvec::prelude::*;

let mut raw = [0u8; 4];
let bits = raw.view_bits_mut::<Msb0>();

let integer = 0x00__B4_96_3Cu32 as i32;
bits[4 .. 28].store_le::<i32>(integer);
let loaded = bits[4 .. 28].load_le::<i32>();
assert_eq!(loaded, 0xFF__B4_96_3Cu32 as i32);

Observe that, because the lowest 24 bits began with the pattern 0b1101…, the value was considered to be negative when interpreted as an i24 and was sign-extended through the highest byte.

Let us now look at the memory representation of this value:

assert_eq!(raw, [
  0b0000_1100,
//  dead 0xC
  0b0110_0011,
//  0x6  0x3
  0b0100_1001,
//  0x4  0x9
  0b1011_0000,
//  0xB  dead
]);

Notice how while the Msb0 bit-ordering means that indexing within the bit-slice proceeds left-to-right in each element, and the bit-patterns in each element proceed left-to-right in the aggregate and the decomposed literals, the ordering of the elements is reversed from how the literal was written.

In the sequence B496, B is the most significant, and so it gets placed highest in memory. 49 fits in one byte, and is stored directly as written. Lastly, 6 is the least significant nibble of the four, and is placed lowest in memory.

Now let’s look at the way different BitOrder parameters interpret the placement of bit indices within memory:

use bitvec::prelude::*;

let raw = [
// Bit index   14 ←
//     Lsb0:  ─┤
            0b0100_0000_0000_0011u16,
//     Msb0:                   ├─
//                           → 14

// Bit index               ← 19 16
//     Lsb0:                 ├──┤
            0b0001_0000_0000_1110u16,
//     Msb0:  ├──┤
//            16 19 →
];

assert_eq!(
  raw.view_bits::<Lsb0>()
     [14 .. 20]
     .load_le::<u8>(),
  0b00_1110_01,
);
assert_eq!(
  raw.view_bits::<Msb0>()
     [14 .. 20]
     .load_le::<u8>(),
  0b00_0001_11,
);

Notice how the bit-orderings change which parts of the memory are loaded, but in both cases the segment in raw[0] is less significant than the segment in raw[1], and the ordering of bits within each segment are unaffected by the bit-ordering.

Notes

Be sure to see the documentation for <BitSlice<_, Lsb0> as BitField>::load_le and <BitSlice<_, Msb0> as Bitfield>::load_le for more detailed information on the memory views!

You can view the mask of all storage regions of a bit-slice by using its .domain() method to view the breakdown of its memory region, then print the .mask() of any PartialElement the domain contains. Whole elements are always used in their entirety. You should use the domain module’s types whenever you are uncertain of the exact locations in memory that a particular bit-slice governs.

Big-Endian Integer Loading

This method loads an integer value from a bit-slice, using big-endian significance ordering when the bit-slice spans more than one T element in memory.

Big-endian significance ordering means that if a bit-slice occupies an array [A, B, C], then the bits stored in A are considered to be the most significant segment of the loaded integer, then B contains the middle segment, then C contains the least significant segment.

The segments are combined in order, that is, as the raw bit-pattern 0b<padding><A><B><C>. If the destination type is signed, the loaded value is sign-extended according to the most-significant bit in the A segment.

It is important to note that the O: BitOrder parameter of the bit-slice from which the value is loaded does not affect the bit-pattern of the stored segments. They are always stored exactly as they exist in an ordinary integer. The ordering parameter only affects which bits in an element are available for storage.

Type Parameters
  • I: The integer type being loaded. This can be any of the signed or unsigned integers.
Parameters
  • &self: A bit-slice region whose length is in the range 1 ..= I::BITS.
Returns

The contents of the bit-slice, interpreted as an integer.

Panics

This panics if self.len() is 0, or greater than I::BITS.

Examples

Let us consider an i32 value stored in 24 bits of a BitSlice<u8, Lsb0>:

use bitvec::prelude::*;

let mut raw = [0u8; 4];
let bits = raw.view_bits_mut::<Lsb0>();

let integer = 0x00__B4_96_3Cu32 as i32;
bits[4 .. 28].store_be::<i32>(integer);
let loaded = bits[4 .. 28].load_be::<i32>();
assert_eq!(loaded, 0xFF__B4_96_3Cu32 as i32);

Observe that, because the lowest 24 bits began with the pattern 0b1101…, the value was considered to be negative when interpreted as an i24 and was sign-extended through the highest byte.

Let us now look at the memory representation of this value:

assert_eq!(raw, [
  0b1011_0000,
//  0xB  dead
  0b0100_1001,
//  0x4  0x9
  0b0110_0011,
//  0x6  0x3
  0b0000_1100,
//  dead 0xC
]);

Notice how while the Lsb0 bit-ordering means that indexing within the bit-slice proceeds right-to-left in each element, the actual bit-patterns stored in memory are not affected. Element [0] is more numerically significant than element [1], but bit [4] is not more numerically significant than bit [5].

In the sequence B496, B is the most significant, and so it gets placed lowest in memory. 49 fits in one byte, and is stored directly as written. Lastly, 6 is the least significant nibble of the four, and is placed highest in memory.

Now let’s look at the way different BitOrder parameters interpret the placement of bit indices within memory:

use bitvec::prelude::*;

let raw = [
// Bit index   14 ←
//     Lsb0:  ─┤
            0b0100_0000_0000_0011u16,
//     Msb0:                   ├─
//                           → 14

// Bit index               ← 19 16
//     Lsb0:                 ├──┤
            0b0001_0000_0000_1110u16,
//     Msb0:  ├──┤
//            16 19 →
];

assert_eq!(
  raw.view_bits::<Lsb0>()
     [14 .. 20]
     .load_be::<u8>(),
  0b00_01_1110,
);
assert_eq!(
  raw.view_bits::<Msb0>()
     [14 .. 20]
     .load_be::<u8>(),
  0b00_11_0001,
);

Notice how the bit-orderings change which parts of the memory are loaded, but in both cases the segment in raw[0] is more significant than the segment in raw[1], and the ordering of bits within each segment are unaffected by the bit-ordering.

Notes

Be sure to see the documentation for <BitSlice<_, Lsb0> as BitField>::load_be and <BitSlice<_, Msb0> as Bitfield>::load_be for more detailed information on the memory views!

You can view the mask of all storage regions of a bit-slice by using its .domain() method to view the breakdown of its memory region, then print the .mask() of any PartialElement the domain contains. Whole elements are always used in their entirety. You should use the domain module’s types whenever you are uncertain of the exact locations in memory that a particular bit-slice governs.

Little-Endian Integer Storing

This method stores an integer value into a bit-slice, using little-endian significance ordering when the bit-slice spans more than one T element in memory.

Little-endian significance ordering means that if a bit-slice occupies an array [A, B, C], then the bits stored in A are considered to contain the least significant segment of the stored integer, then B contains the middle segment, and then C contains the most significant segment.

An integer is broken into segments in order, that is, the raw bit-pattern is fractured into 0b<padding><C><B><A>. High bits beyond the length of the bit-slice into which the integer is stored are truncated.

It is important to note that the O: BitOrder parameter of the bit-slice into which the value is stored does not affect the bit-pattern of the stored segments. They are always stored exactly as they exist in an ordinary integer. The ordering parameter only affects which bits in an element are available for storage.

Type Parameters
  • I: The integer type being stored. This can be any of the signed or unsigned integers.
Parameters
  • &mut self: A bit-slice region whose length is in the range 1 ..= I::BITS.
  • value: An integer value whose self.len() least numerically significant bits will be written into self.
Panics

This panics if self.len() is 0, or greater than I::BITS.

Examples

Let us consider an i32 value stored in 24 bits of a BitSlice<u8, Msb0>:

use bitvec::prelude::*;

let mut raw = [0u8; 4];
let bits = raw.view_bits_mut::<Msb0>();

let integer = 0x00__B4_96_3Cu32 as i32;
bits[4 .. 28].store_le::<i32>(integer);
let loaded = bits[4 .. 28].load_le::<i32>();
assert_eq!(loaded, 0xFF__B4_96_3Cu32 as i32);

Observe that, because the lowest 24 bits began with the pattern 0b1101…, the value was considered to be negative when interpreted as an i24 and was sign-extended through the highest byte.

Let us now look at the memory representation of this value:

assert_eq!(raw, [
  0b0000_1100,
//  dead 0xC
  0b0110_0011,
//  0x6  0x3
  0b0100_1001,
//  0x4  0x9
  0b1011_0000,
//  0xB  dead
]);

Notice how while the Msb0 bit-ordering means that indexing within the bit-slice proceeds left-to-right in each element, and the bit-patterns in each element proceed left-to-right in the aggregate and the decomposed literals, the ordering of the elements is reversed from how the literal was written.

In the sequence B496, B is the most significant, and so it gets placed highest in memory. 49 fits in one byte, and is stored directly as written. Lastly, 6 is the least significant nibble of the four, and is placed lowest in memory.

Now let’s look at the way different BitOrder parameters interpret the placement of bit indices within memory:

use bitvec::prelude::*;

let raw = [
// Bit index   14 ←
//     Lsb0:  ─┤
            0b0100_0000_0000_0011u16,
//     Msb0:                   ├─
//                           → 14

// Bit index               ← 19 16
//     Lsb0:                 ├──┤
            0b0001_0000_0000_1110u16,
//     Msb0:  ├──┤
//            16 19 →
];

assert_eq!(
  raw.view_bits::<Lsb0>()
     [14 .. 20]
     .load_le::<u8>(),
  0b00_1110_01,
);
assert_eq!(
  raw.view_bits::<Msb0>()
     [14 .. 20]
     .load_le::<u8>(),
  0b00_0001_11,
);

Notice how the bit-orderings change which parts of the memory are loaded, but in both cases the segment in raw[0] is less significant than the segment in raw[1], and the ordering of bits within each segment are unaffected by the bit-ordering.

Notes

Be sure to see the documentation for <BitSlice<_, Lsb0> as BitField>::store_le and <BitSlice<_, Msb0> as Bitfield>::store_le for more detailed information on the memory views!

You can view the mask of all storage regions of a bit-slice by using its .domain() method to view the breakdown of its memory region, then print the .mask() of any PartialElement the domain contains. Whole elements are always used in their entirety. You should use the domain module’s types whenever you are uncertain of the exact locations in memory that a particular bit-slice governs.

Big-Endian Integer Storing

This method stores an integer value into a bit-slice, using big-endian significance ordering when the bit-slice spans more than one T element in memory.

Big-endian significance ordering means that if a bit-slice occupies an array [A, B, C], then the bits stored in A are considered to contain the most significant segment of the stored integer, then B contains the middle segment, and then C contains the least significant segment.

An integer is broken into segments in order, that is, the raw bit-pattern is fractured into 0b<padding><A><B><C>. High bits beyond the length of the bit-slice into which the integer is stored are truncated.

It is important to note that the O: BitOrder parameter of the bit-slice into which the value is stored does not affect the bit-pattern of the stored segments. They are always stored exactly as they exist in an ordinary integer. The ordering parameter only affects which bits in an element are available for storage.

Type Parameters
  • I: The integer type being stored. This can be any of the signed or unsigned integers.
Parameters
  • &mut self: A bit-slice region whose length is in the range 1 ..= I::BITS.
  • value: An integer value whose self.len() least numerically significant bits will be written into self.
Panics

This panics if self.len() is 0, or greater than I::BITS.

Examples

Let us consider an i32 value stored in 24 bits of a BitSlice<u8, Lsb0>:

use bitvec::prelude::*;

let mut raw = [0u8; 4];
let bits = raw.view_bits_mut::<Lsb0>();

let integer = 0x00__B4_96_3Cu32 as i32;
bits[4 .. 28].store_be::<i32>(integer);
let loaded = bits[4 .. 28].load_be::<i32>();
assert_eq!(loaded, 0xFF__B4_96_3Cu32 as i32);

Observe that, because the lowest 24 bits began with the pattern 0b1101…, the value was considered to be negative when interpreted as an i24 and was sign-extended through the highest byte.

Let us now look at the memory representation of this value:

assert_eq!(raw, [
  0b1011_0000,
//  0xB  dead
  0b0100_1001,
//  0x4  0x9
  0b0110_0011,
//  0x6  0x3
  0b0000_1100,
//  dead 0xC
]);

Notice how while the Lsb0 bit-ordering means that indexing within the bit-slice proceeds right-to-left in each element, the actual bit-patterns stored in memory are not affected. Element [0] is more numerically significant than element [1], but bit [4] is not more numerically significant than bit [5].

In the sequence B496, B is the most significant, and so it gets placed lowest in memory. 49 fits in one byte, and is stored directly as written. Lastly, 6 is the least significant nibble of the four, and is placed highest in memory.

Now let’s look at the way different BitOrder parameters interpret the placement of bit indices within memory:

use bitvec::prelude::*;

let raw = [
// Bit index   14 ←
//     Lsb0:  ─┤
            0b0100_0000_0000_0011u16,
//     Msb0:                   ├─
//                           → 14

// Bit index               ← 19 16
//     Lsb0:                 ├──┤
            0b0001_0000_0000_1110u16,
//     Msb0:  ├──┤
//            16 19 →
];

assert_eq!(
  raw.view_bits::<Lsb0>()
     [14 .. 20]
     .load_be::<u8>(),
  0b00_01_1110,
);
assert_eq!(
  raw.view_bits::<Msb0>()
     [14 .. 20]
     .load_be::<u8>(),
  0b00_11_0001,
);

Notice how the bit-orderings change which parts of the memory are loaded, but in both cases the segment in raw[0] is more significant than the segment in raw[1], and the ordering of bits within each segment are unaffected by the bit-ordering.

Notes

Be sure to see the documentation for <BitSlice<_, Lsb0> as BitField>::store_be and <BitSlice<_, Msb0> as Bitfield>::store_be for more detailed information on the memory views!

You can view the mask of all storage regions of a bit-slice by using its .domain() method to view the breakdown of its memory region, then print the .mask() of any PartialElement the domain contains. Whole elements are always used in their entirety. You should use the domain module’s types whenever you are uncertain of the exact locations in memory that a particular bit-slice governs.

Provided Methods

Integer Loading

This method reads the contents of a bit-slice region as an integer. The region may be shorter than the destination integer type, in which case the loaded value will be zero-extended (when I: Unsigned) or sign-extended from the most significant loaded bit (when I: Signed).

The region may not be zero bits, nor wider than the destination type. Attempting to load a u32 from a bit-slice of length 33 will panic the program.

Operation and Endianness Handling

Each element in the bit-slice contains a segment of the value to be loaded. If the bit-slice contains more than one element, then the numerical significance of each loaded segment is interpreted according to the target’s endianness:

  • little-endian targets consider each T element to have increasing numerical significance, starting with the least-significant segment at the low address and ending with the most-significant segment at the high address.
  • big-endian targets consider each T element to have decreasing numerical significance, starting with the most-significant segment at the high address and ending with the least-significant segment at the low address.

See the documentation for .load_le() and .load_be() for more detail on what this means for how the in-memory representation of bit-slices translates to loaded values.

You must always use the loading method that exactly corresponds to the storing method previously used to insert data into the bit-slice: same suffix on the method name (none, _le, _be) and same integer type. bitvec is not required to, and will not, guarantee round-trip consistency if you change any of these parameters.

Type Parameters
  • I: The integer type being loaded. This can be any of the signed or unsigned integers.
Parameters
  • &self: A bit-slice region whose length is in the range 1 ..= I::BITS.
Returns

The contents of the bit-slice, interpreted as an integer.

Panics

This panics if self.len() is 0, or greater than I::BITS.

Examples

This method is inherently non-portable, and changes behavior depending on the target characteristics. If your target is little-endian, see .load_le(); if your target is big-endian, see .load_be().

Integer Storing

This method writes an integer into the contents of a bit-slice region. The region may be shorter than the source integer type, in which case the stored value will be truncated. On load, it may be zero-extended (unsigned destination) or sign-extended from the most significant stored bit (signed destination).

The region may not be zero bits, nor wider than the source type. Attempting to store a u32 into a bit-slice of length 33 will panic the program.

Operation and Endianness Handling

The value to be stored is broken into segments according to the elements of the bit-slice receiving it. If the bit-slice contains more than one element, then the numerical significance of each segment routes to a storage element according to the target’s endianness:

  • little-endian targets consider each T element to have increasing numerical significance, starting with the least-significant segment at the low address and ending with the most-significant segment at the high address.
  • big-endian targets consider each T element to have decreasing numerical significance, starting with the most-significant segment at the high address and ending with the least-significant segment at the low address.

See the documentation for .store_le() and .store_be() for more detail on what this means for how the in-memory representation of bit-slices translates to stored values.

You must always use the loading method that exactly corresponds to the storing method previously used to insert data into the bit-slice: same suffix on the method name (none, _le, _be) and same integer type. bitvec is not required to, and will not, guarantee round-trip consistency if you change any of these parameters.

Type Parameters
  • I: The integer type being stored. This can be any of the signed or unsigned integers.
Parameters
  • &self: A bit-slice region whose length is in the range 1 ..= I::BITS.
  • value: An integer value whose self.len() least numerically significant bits will be written into self.
Panics

This panics if self.len() is 0, or greater than I::BITS.

Examples

This method is inherently non-portable, and changes behavior depending on the target characteristics. If your target is little-endian, see .store_le(); if your target is big-endian, see .store_be().

Implementors

Bit-Array Implementation of BitField

The BitArray implementation is only ever called when the entire bit-array is available for use, which means it can skip the bit-slice memory detection and instead use the underlying storage elements directly.

The implementation still performs the segmentation for each element contained in the array, in order to maintain value consistency so that viewing the array as a bit-slice is still able to correctly interact with data contained in it.

Lsb0 Bit-Field Behavior

BitField has no requirements about the in-memory representation or layout of stored integers within a bit-slice, only that round-tripping an integer through a store and a load of the same element suffix on the same bit-slice is idempotent (with respect to sign truncation).

Lsb0 provides a contiguous translation from bit-index to real memory: for any given bit index n and its position P(n), P(n + 1) is P(n) + 1. This allows it to provide batched behavior: since the section of contiguous indices used within an element translates to a section of contiguous bits in real memory, the transaction is always a single shift/mask operation.

Each implemented method contains documentation and examples showing exactly how the abstract integer space is mapped to real memory.

Msb0 Bit-Field Behavior

BitField has no requirements about the in-memory representation or layout of stored integers within a bit-slice, only that round-tripping an integer through a store and a load of the same element suffix on the same bit-slice is idempotent (with respect to sign truncation).

Msb0 provides a contiguous translation from bit-index to real memory: for any given bit index n and its position P(n), P(n + 1) is P(n) - 1. This allows it to provide batched behavior: since the section of contiguous indices used within an element translates to a section of contiguous bits in real memory, the transaction is always a single shift-mask operation.

Each implemented method contains documentation and examples showing exactly how the abstract integer space is mapped to real memory.

Notes

In particular, note that while Msb0 indexes bits from the most significant down to the least, and integers index from the least up to the most, this does not reörder any bits of the integer value! This ordering only finds a region in real memory; it does not affect the partial-integer contents stored in that region.