4.2 KiB
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.