A Vec of Bits
README.md

stackoverflow question#

Rust: Mark the method as unsafe, or just add "unsafe" to its name? Dilemma with library API design.

Hi. I am maintaining a dynamic array of bits just like C++'s vector<bool>.

The thing in question, is the method for getting access to the underlying Vec<u32>, which may let the caller mess up the dynamic array. It's not inherently memory-unsafe, but currently marked as such. My idea is to change this unsafe fn to be marked as safe, while adding a prefix unsafe_ to its name.

code review request#

maintainer of bit-vec here. It's a Rust library for lists of booleans.

The library is used by a couple thousand people, so I'd appreciate thorough review. You have a compact dynamic arrays of bits like vector<bool> in C++ and fill all elements with the given value, or remove one of the elements at the given index. This is basically it. But I had to deprecate fn clear by renaming it to fn fill because the name was inconsistent with other collections having clear truncate the list to zero elements: https://github.com/contain-rs/bit-vec/issues/16

You may see the code here: https://github.com/contain-rs/bit-vec/pull/134/changes https://github.com/contain-rs/bit-vec/pull/135/changes All of it except the tests is included below.

    /// Assigns all bits in this vector to the given boolean value.
    ///
    /// # Invariants
    ///
    /// - After a call to `.fill(true)`, the result of [`all`] is `true`.
    /// - After a call to `.fill(false)`, the result of [`none`] is `true`.
    ///
    /// [`all`]: Self::all
    /// [`none`]: Self::none
    #[inline]
    pub fn fill(&mut self, bit: bool) {
        self.ensure_invariant();
        let block = if bit { !B::zero() } else { B::zero() };
        for w in &mut self.storage {
            *w = block;
        }
        if bit {
            self.fix_last_block();
        }
    }

    /// Clears all bits in this vector.
    #[inline]
    #[deprecated(since = "0.9.0", note = "please use `.fill(false)` instead")]
    pub fn clear(&mut self) {
        self.ensure_invariant();
        for w in &mut self.storage {
            *w = B::zero();
        }
    }

    /// Remove a bit at index `at`, shifting all bits after by one.
    ///
    /// # Panics
    /// Panics if `at` is out of bounds for `BitVec`'s length (that is, if `at >= BitVec::len()`)
    ///
    /// # Examples
    ///```
    /// use bit_vec::BitVec;
    ///
    /// let mut b = BitVec::new();
    ///
    /// b.push(true);
    /// b.push(false);
    /// b.push(false);
    /// b.push(true);
    /// assert!(!b.remove(1));
    ///
    /// assert!(b.eq_vec(&[true, false, true]));
    ///```
    ///
    /// # Time complexity
    /// Takes O([`len`]) time. All items after the removal index must be
    /// shifted to the left. In the worst case, all elements are shifted when
    /// the removal index is 0.
    ///
    /// [`len`]: Self::len
    pub fn remove(&mut self, at: usize) -> bool {
        assert!(
            at < self.nbits,
            "removal index (is {at}) should be < len (is {nbits})",
            nbits = self.nbits
        );
        self.ensure_invariant();

        self.nbits -= 1;

        let last_block_bits = self.nbits % B::bits();
        let block_at = at / B::bits(); // needed block
        let bit_at = at % B::bits(); // index within the block

        let lsbits_mask = (B::one() << bit_at) - B::one();

        let mut carry = B::zero();

        for block_ref in self.storage[block_at + 1..].iter_mut().rev() {
            let curr_carry = *block_ref & B::one();
            *block_ref = *block_ref >> 1 | (carry << (B::bits() - 1));
            carry = curr_carry;
        }

        // Safety: thanks to the assert above.
        let result = unsafe { self.get_unchecked(at) };

        self.storage[block_at] = (self.storage[block_at] & lsbits_mask)
            | ((self.storage[block_at] & (!lsbits_mask << 1)) >> 1)
            | carry << (B::bits() - 1);

        if last_block_bits == 0 {
            self.storage.pop();
        }

        result
    }
pub struct BitVec<B = u32> {
    /// Internal representation of the bit vector
    storage: Vec<B>,
    /// The number of valid bits in the internal representation
    nbits: usize,
}

/// Abstracts over a pile of bits (basically unsigned primitives)
pub trait BitBlock:
    Copy
    + Add<Self, Output = Self>
    + Sub<Self, Output = Self>
    + Shl<usize, Output = Self>
    + Shr<usize, Output = Self>
    + Not<Output = Self>
    + BitAnd<Self, Output = Self>
    + BitOr<Self, Output = Self>
    + BitXor<Self, Output = Self>
    + Rem<Self, Output = Self>
    + Eq
    + Ord
    + hash::Hash
{
    /// How many bits it has
    fn bits() -> usize;
    /// How many bytes it has
    #[inline]
    fn bytes() -> usize {
        Self::bits() / 8
    }
    /// Convert a byte into this type (lowest-order bits set)
    fn from_byte(byte: u8) -> Self;
    /// Count the number of 1's in the bitwise repr
    fn count_ones(self) -> usize;
    /// Count the number of 0's in the bitwise repr
    fn count_zeros(self) -> usize {
        Self::bits() - self.count_ones()
    }
    /// Get `0`
    fn zero() -> Self;
    /// Get `1`
    fn one() -> Self;
}

macro_rules! bit_block_impl {
    ($(($t: ident, $size: expr)),*) => ($(
        impl BitBlock for $t {
            #[inline]
            fn bits() -> usize { $size }
            #[inline]
            fn from_byte(byte: u8) -> Self { $t::from(byte) }
            #[inline]
            fn count_ones(self) -> usize { self.count_ones() as usize }
            #[inline]
            fn count_zeros(self) -> usize { self.count_zeros() as usize }
            #[inline]
            fn one() -> Self { 1 }
            #[inline]
            fn zero() -> Self { 0 }
        }
    )*)
}

bit_block_impl! {
    (u8, 8),
    (u16, 16),
    (u32, 32),
    (u64, 64),
    (usize, core::mem::size_of::<usize>() * 8)
}