Arrays
While BitSlice
describes a region of borrowed data, BitArray
provides a
container that can hold and manage such a region.
It is most comparable to the C++ type std::bitset<N>
. Unfortunately, the
Rust support for type-level integers is still experimental, so it is unable to
take the length of the BitSlice
it contains as a type parameter. Instead, it
must take the entire region it contains as a type parameter. The full type
declaration is
use bitvec::prelude::*;
pub struct BitArray<
A: BitViewSized,
O: BitOrder,
> {
_ord: PhantomData<O>,
data: A,
}
As described in the previous chapter, the BitView
trait is implemented on
the unsigned integers, and on arrays of them.
Once type-level computation stabilizes, `BitArray` will change to have the type
parameters `<T: BitStore, O: BitOrder, const N: usize>`, matching the
`std::bitset<N>` length parameter. This will require a major-version increase.
This array dereferences to a BitSlice
region over its entire length. It does
not currently permit shortening its BitSlice
from either end. If this is a
behavior you want, please file an issue.
Declaring a BitArray
Type
Until Rust allows type-level integer computation to affect the memory layout,
BitArray
types remain awkward to declare. You could declare types yourself by
using the bitvec::mem::elts
function:
use bitvec::{array::BitArray, mem, order::Lsb0};
type MyArray = BitArray<[u8; mem::elts::<u8>(50)], Lsb0>;
But for convenience, we provide a BitArr!
type-declaration macro. It expands
to exactly the expression above. It accepts the following syntaxes:
use bitvec::{BitArr, order::Lsb0};
// explicit ordering
type A = BitArr!(for 50, in u16, Lsb0);
// implicit ordering defaults to Lsb0
type B = BitArr!(for 50, in u32);
// implicit store defaults to usize
type C = BitArr!(for 50);
Creating a BitArray
Value
The ::ZERO
constant is a blank BitArray
with its memory completely zeroed.
The ::new()
function wraps an existing element or array into a BitArray
. In
addition, the macro constructor bitarr!
takes the exact same arguments as the
bits!
constructor, except that it returns an array directly rather than a
reference to a buffer.
Furthermore, BitArray
structures and references can be constructed from
&BitSlice
references using the TryFrom
trait, just as arrays can be
constructed in the standard library.
Using a BitArray
Once constructed, BitArray
offers the .as_bitslice()
and
.as_mut_bitslice()
explicit methods, as well as all the standard traits, to
borrow its data as a BitSlice
. The array has almost no functionality of its
own, and serves primarily to own a region used as a BitSlice
. Like standard
library arrays, it natively produces a by-value iterator
Once you are done using BitSlice
to manipulate the array, you can remove the
array with .into_inner()
and regain the A
memory within.
That’s everything that the array does! Like regular arrays, it is useful
primarily for its ability to move memory through a program, and has essentially
no behavior in its own right. As a plain data structure, it is most useful for
programs that do not have access to a dynamic allocator, and do not wish to use
static
buffers. However, if you do have access to an allocator, you will
probably prefer to use BitVec
instead.
BitArray
is uniquely suited as a structural field for types which implement
I/O protocols and have fixed-size buffers, such as the headers of internet
transport packets, or CPU instruction set encodings. A full example can be found
in the bitvec
examples, but here is a short sample of how a TCP packet
header might be represented:
use bitvec::prelude::*;
#[derive(Clone, Copy, Default)]
struct TcpHeader {
data: BitArr!(for 160, in u8; Msb0),
}
impl TcpHeader {
fn data_offset(&self) -> usize {
self.data[96 .. 100].load::<u8>() as usize
}
fn set_data_offset(&mut self, value: u8) {
if !(5 ..= 15).contains(&value) {
panic!("invalid data offset value");
}
self.data[96 .. 100].store(value);
}
fn syn_flag(&self) -> bool {
self.data[110]
}
fn sequence_number(&self) -> u32 {
self.data[32 .. 64].load_be()
}
fn as_bytes(&self) -> &[u8] {
self.data.as_raw_slice()
}
}
This snippet shows how you can:
- use
BitArray
as the storage inside a semantic new-type - select individual flag bits
- use sub-byte regions for small integer storage
- use multi-byte regions for large integer storage
- access the raw storage for interaction with I/O systems