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