Headers are counted in bytes, payloads in bits, so the two need a clean seam. Today you add byte alignment plus a big-endian 32-bit field - the primitive every container header uses to record an original length.
Add Align plus WriteUint32 and ReadUint32, and round-trip a length field written after some bits.
A compressed file is part header, part packed payload, and the header is easiest
to read as whole bytes. Byte alignment is the seam between the two: Align
finishes off any partially filled byte (padding it) so the next thing you write
starts on a byte boundary. After writing three bits, Align emits 0xA0, and a
big-endian WriteUint32(0x01020304) appends 0x01, 0x02, 0x03, 0x04 right after.
The reader’s Align is the mirror: if it is mid-byte, it skips the rest of that
byte before reading the next field. Big-endian order (most significant byte
first) matches the most-significant-first convention already used for bits, so the
whole stream reads left to right, high to low. This little pair - align, then a
32-bit field - is exactly what a container header needs to store the original
length of the data, so the decoder knows how many bytes to expect. With the bit
layer complete and reversible, the real codecs can begin.
// Align finishes the current byte so the next write starts cleanfunc (w *BitWriter) Align() { if w.nbit > 0 { w.out = append(w.out, w.cur); w.cur = 0; w.nbit = 0 } }// WriteUint32 assumes the stream is byte-aligned: call Align first.func (w *BitWriter) WriteUint32(v uint32) {w.out = append(w.out, byte(v>>24), byte(v>>16), byte(v>>8), byte(v)) // big-endian}// reader Align: if nbit>0, advance to the next byte and reset nbit