cargo / bit-set / audit
cargo : bit-set @ 0.5.3
PE Patrick Elsen signed 2026-05-27 published 2026-05-27

Claims

datastructure-impl-boundsdatastructure-impl-correctdatastructure-impl-safedatastructure-impl-testedhas-binarieshas-build-exechas-fuzz-testshas-install-exechas-integration-testshas-property-testshas-unit-testsimpl-algorithmimpl-concurrencyimpl-cryptoimpl-datastructureimpl-interpreterimpl-jitimpl-parserimpl-protocolis-benignunsafe-documentedunsafe-minimalunsafe-safeunsafe-testeduses-concurrencyuses-cryptouses-environmentuses-execuses-filesystemuses-interpreteruses-jituses-networkuses-unsafe

Summary

no_std set built on bit-vec; two unsafe call sites to storage_mut/set_len preserve the bit-length-vs-storage-length invariant. Four low-severity quality findings: VCS SHA does not exist upstream, unsafe blocks lack SAFETY comments, no randomized testing, stale README badges and maintenance-mode notice.

Report

Subject

bit-set is a no_std Rust library providing a BitSet<B> collection over the bit-vec crate. It exposes the standard set interface (insert, remove, contains, len, clear), the four binary set operations (union, intersection, difference, symmetric_difference, in both iterator and in-place forms), and the usual containment predicates (is_subset, is_disjoint, is_superset). The block type is generic over the bit_vec::BitBlock trait and defaults to u32.

Methodology

The published crate contents were compared against the upstream Git repository at https://github.com/contain-rs/bit-set using diff -r. The commit referenced in .cargo_vcs_info.json (see FINDING-1) is not reachable, so the comparison was performed against the v0.5.3 tag (commit 6ecf66b); the single source file src/lib.rs matches byte-for-byte. The 1608-line src/lib.rs was read in full, with particular attention to the two unsafe blocks, the iterator engine BlockIter, the storage-sizing helper blocks_for_bits, and the shrink_to_fit path the 0.5.3 release was cut to fix. The package metadata (Cargo.toml, Cargo.toml.orig, .cargo_vcs_info.json, .travis.yml, deploy-docs.sh, README.md, both licence files) was reviewed individually. The test suite was inspected statically but not executed.

Results

The package contains no binary artefacts (justifying has-binaries), no build.rs (justifying has-build-exec), and no [lib] proc-macro = true declaration; cargo runs no install-time hooks for ordinary library crates either (justifying has-install-exec). The single non-Rust executable in the tree, deploy-docs.sh, is invoked only by the (now defunct) Travis CI pipeline and is not wired into the build, install, or runtime path. The src/lib.rs review found no std::net, std::fs, std::process, std::env, std::thread, or async constructs anywhere in the file, justifying uses-network, uses-filesystem, uses-exec, uses-environment, uses-concurrency, uses-jit, and uses-interpreter. The crate performs no cryptographic operations and does not embed a parser, interpreter, JIT, protocol implementation, or concurrency primitive, justifying uses-crypto, impl-crypto, impl-parser, impl-interpreter, impl-jit, impl-protocol, impl-algorithm, impl-concurrency.

The two unsafe blocks in the crate (other_op at src/lib.rs:379, shrink_to_fit at src/lib.rs:417) call into bit_vec's storage_mut and set_len. In both cases the bit-length-vs-storage-length invariant required by bit-vec is preserved: other_op only overwrites existing slots after growing self to cover other's length, and shrink_to_fit resets the bit length in lock-step with the truncated storage. The unsafe is the minimum needed to call those APIs (justifying unsafe-minimal) and is sound (justifying unsafe-safe), but lacks // SAFETY: comments (see FINDING-2, justifying unsafe-documented = false).

The dataset of 24 in-module unit tests covers each public operation including the previously-broken empty-shrink case the 0.5.3 release was cut to fix; there is no tests/ directory, justifying has-integration-tests = false. There is no property or fuzz harness, justifying has-property-tests, has-fuzz-tests, unsafe-tested, and datastructure-impl-tested all false; see FINDING-3. Storage scaling is documented (the crate-level rustdoc states storage is proportional to the maximum element value), operations on the bit storage are linear-time in block count, and blocks_for_bits is overflow-safe, justifying datastructure-impl-bounds.

The audit produced four low-severity findings, all in the quality class: a broken VCS pointer (FINDING-1), missing SAFETY comments (FINDING-2), absent randomized testing (FINDING-3), and a stale README (FINDING-4). None affects the runtime behaviour or safety of the library.

Conclusion

bit-set@0.5.3 is a small, well-scoped, no_std-friendly data-structure crate with a narrow attack surface: no I/O, no concurrency, no build-time or install-time code execution, no transitive heavy dependencies, and only two well-contained unsafe blocks. The findings are exclusively documentation and process gaps. No malicious behaviour, obfuscation, or unexpected side effects were observed, justifying is-benign. The crate is in maintenance mode per its README.

Findings(4)

FINDING-1 quality low

.cargo_vcs_info.json references a non-existent commit

The published crate's .cargo_vcs_info.json records the source revision as 9f1d0b3342f1defdd5ac59566dc6cff698d622a2, but that commit does not exist in the upstream repository at https://github.com/contain-rs/bit-set. This breaks the standard audit path of pinning the VCS checkout via the recorded SHA and forces auditors to fall back to the v0.5.3 tag (commit 6ecf66b). The source code in contents/src/lib.rs matches the v0.5.3 tag byte-for-byte, so this is a traceability issue and not a content discrepancy.

FINDING-2 quality low

Unsafe blocks lack SAFETY comments

Both unsafe blocks in the crate lack explanatory safety comments justifying uses-unsafe.

  • src/lib.rs:379 (other_op): unsafe { self_bit_vec.storage_mut()[i] = new; }
  • src/lib.rs:417 (shrink_to_fit): unsafe { bit_vec.storage_mut().truncate(trunc_len); bit_vec.set_len(trunc_len * B::bits()); bit_vec.shrink_to_fit(); }

Both calls are sound (in other_op the storage length is unchanged; in shrink_to_fit the bit-length is reset in lock-step with the truncated storage), but the absence of // SAFETY: comments makes future review harder and is the reason unsafe-documented is asserted false.

FINDING-3 quality low

No property tests or fuzz tests for set invariants

The crate has 24 unit tests covering basic set operations but no property-based or fuzz tests, justifying has-property-tests, has-fuzz-tests, unsafe-tested, and datastructure-impl-tested. The 0.5.3 release commit message states that this version fixes a bug where shrink_to_fit did not actually shrink and would panic if it emptied the collection — a class of correctness regression that randomized testing would have caught. Adding property tests (e.g. via proptest) comparing BitSet against a reference HashSet model would meaningfully raise confidence in the invariants of other_op, shrink_to_fit, and the iteration order of Iter/Union/Intersection.

FINDING-4 quality low

README contains dead badges and unmaintained-project notice

The published README.md opens with a notice that the project is in maintenance mode and accepts only minimal-review changes. It also links to two badges that no longer resolve:

  • http://meritbadge.herokuapp.com/bit-set — the meritbadge service has been retired.
  • https://travis-ci.org/contain-rs/bit-set.svg?branch=master — Travis CI for open-source is no longer operating.

The upstream repository has since replaced these with GitHub Actions and shields.io badges, but the published 0.5.3 crate still ships the stale URLs.

Annotations(5)

.cargo_vcs_info.json

Records source revision 9f1d0b3342f1defdd5ac59566dc6cff698d622a2, which is unreachable from any branch or tag in the upstream repo; see FINDING-1. The package content nevertheless matches the v0.5.3 tag (commit 6ecf66b) exactly, so the divergence is in VCS metadata only.

Cargo.toml

Cargo.toml, line 27-29

[dependencies.bit-vec]
version = "0.6.1"
default-features = false

Single runtime dependency is bit-vec (the underlying bit-vector type), with no transitive crypto, network, filesystem, exec, environment, or concurrency surface. No build-dependencies section and no [lib] proc-macro = true. Justifies has-build-exec, has-install-exec.

Cargo.toml, line 34-36

[features]
default = ["std"]
std = ["bit-vec/std"]

Only feature is std, on by default, which forwards to bit-vec/std. The crate itself is #![no_std] (see src/lib.rs:50); disabling std removes the test harness only.

README.md

Documents the public API at a high level and announces maintenance-mode status; see FINDING-4 for the dead badge URLs the published file ships.

deploy-docs.sh

Shipped in the published crate but only intended for Travis CI consumption: it pushes generated docs to the gh-pages branch using a $GH_TOKEN from the CI environment. It is not invoked at install time, build time, or runtime by any consumer (no build.rs, no Cargo.toml reference), so it does not constitute install-time or build-time code execution.

src/lib.rs

src/lib.rs, line 119-121

pub struct BitSet<B = u32> {
    bit_vec: BitVec<B>,
}

BitSet<B = u32> is a thin wrapper over bit_vec::BitVec<B> exposing set semantics. This is the principal data structure justifying impl-datastructure. The struct holds no other state; storage and bit-length invariants are delegated to bit-vec.

src/lib.rs, line 375-383

        // Apply values found in other
        for (i, w) in other_words {
            let old = self_bit_vec.storage()[i];
            let new = f(old, w);
            unsafe {
                self_bit_vec.storage_mut()[i] = new;
            }
        }
    }

First unsafe block (other_op). The block calls bit_vec.storage_mut() (unsafe in bit-vec because the caller is responsible for keeping the bit length consistent with the storage). Here only existing storage slots are overwritten with a value computed from the same slot; storage length is not changed, and self_bit_vec was grown beforehand (line 365-367) so that every index visited by other_words is in-bounds. The invariant holds, so the block is sound — see FINDING-2 (no SAFETY comment is the documentation gap). Justifies uses-unsafe, unsafe-safe, unsafe-minimal.

src/lib.rs, line 403-422

    #[inline]
    pub fn shrink_to_fit(&mut self) {
        let bit_vec = &mut self.bit_vec;
        // Obtain original length
        let old_len = bit_vec.storage().len();
        // Obtain coarse trailing zero length
        let n = bit_vec
            .storage()
            .iter()
            .rev()
            .take_while(|&&n| n == B::zero())
            .count();
        // Truncate away all empty trailing blocks, then shrink_to_fit
        let trunc_len = old_len - n;
        unsafe {
            bit_vec.storage_mut().truncate(trunc_len);
            bit_vec.set_len(trunc_len * B::bits());
            bit_vec.shrink_to_fit();
        }
    }

Second unsafe block (shrink_to_fit). Counts the trailing zero-blocks and truncates storage to trunc_len blocks, then calls the unsafe set_len to set the logical bit count to trunc_len * B::bits(). The two operations are paired so that the bit-length never exceeds the storage capacity, which is the invariant bit_vec requires. The v0.5.3 release commit message notes this method was rewritten to fix an earlier non-shrinking / panicking implementation; the fix is correct for both the new-empty and shrink-non-empty cases (see test_bit_set_shrink_to_fit_new and test_bit_set_shrink_to_fit). The unsafe is necessary, supporting unsafe-safe, unsafe-minimal.

src/lib.rs, line 891-924

impl<'a, T, B: BitBlock> Iterator for BlockIter<T, B>
where
    T: Iterator<Item = B>,
{
    type Item = usize;

    fn next(&mut self) -> Option<usize> {
        while self.head == B::zero() {
            match self.tail.next() {
                Some(w) => self.head = w,
                None => return None,
            }
            self.head_offset += B::bits();
        }

        // from the current block, isolate the
        // LSB and subtract 1, producing k:
        // a block with a number of set bits
        // equal to the index of the LSB
        let k = (self.head & (!self.head + B::one())) - B::one();
        // update block, removing the LSB
        self.head = self.head & (self.head - B::one());
        // return offset + (index of LSB)
        Some(self.head_offset + (B::count_ones(k) as usize))
    }

    #[inline]
    fn size_hint(&self) -> (usize, Option<usize>) {
        match self.tail.size_hint() {
            (_, Some(h)) => (0, Some(1 + h * B::bits())),
            _ => (0, None),
        }
    }
}

BlockIter::next is the inner iteration engine used by Iter, Union, Intersection, Difference, and SymmetricDifference. It pulls blocks via the tail iterator, isolates the lowest set bit via the two's-complement LSB trick (AND with negation-plus-one), and returns head_offset + popcount(k). The arithmetic uses only the generic BitBlock trait operations on unsigned types, so no overflow or signed-shift UB. Iteration order is ascending block-then-bit, which the unit tests rely on (e.g. test_bit_set_intersection expects [3, 5, 11, 77]). Supports datastructure-impl-correct.

src/lib.rs, line 71-86

/// Computes how many blocks are needed to store that many bits
fn blocks_for_bits<B: BitBlock>(bits: usize) -> usize {
    // If we want 17 bits, dividing by 32 will produce 0. So we add 1 to make sure we
    // reserve enough. But if we want exactly a multiple of 32, this will actually allocate
    // one too many. So we need to check if that's the case. We can do that by computing if
    // bitwise AND by `32 - 1` is 0. But LLVM should be able to optimize the semantically
    // superior modulo operator on a power of two to this.
    //
    // Note that we can technically avoid this branch with the expression
    // `(nbits + BITS - 1) / 32::BITS`, but if nbits is almost usize::MAX this will overflow.
    if bits % B::bits() == 0 {
        bits / B::bits()
    } else {
        bits / B::bits() + 1
    }
}

blocks_for_bits cannot overflow: the result of bits / B::bits() is strictly less than usize::MAX for any B::bits() >= 2 (true for all unsigned integer block types), so + 1 in the else branch is safe. The inline comment correctly explains that the unbranched alternative (nbits + BITS - 1) / BITS would overflow when nbits is near usize::MAX. Supports datastructure-impl-bounds.

src/lib.rs, line 50-67

#![no_std]
#![cfg_attr(all(test, feature = "nightly"), feature(test))]
extern crate bit_vec;
#[cfg(all(test, feature = "nightly"))]
extern crate rand;
#[cfg(all(test, feature = "nightly"))]
extern crate test;

#[cfg(test)]
#[macro_use]
extern crate std;

use bit_vec::{BitBlock, BitVec, Blocks};
use core::cmp;
use core::cmp::Ordering;
use core::fmt;
use core::hash;
use core::iter::{self, Chain, Enumerate, FromIterator, Repeat, Skip, Take};

Crate is #![no_std]. Imports are limited to core::cmp, core::fmt, core::hash, and core::iter; the only third-party crate is bit_vec. std is only used inside #[cfg(test)] (the test module relies on Vec and format!). There are no std::net, std::fs, std::process, std::env, std::thread, or async imports anywhere in the file, justifying uses-network, uses-filesystem, uses-exec, uses-environment, uses-concurrency, uses-crypto, uses-jit, uses-interpreter, impl-crypto, impl-parser, impl-interpreter, impl-jit, impl-protocol, impl-algorithm, impl-concurrency.

src/lib.rs, line 1026-1473

#[cfg(test)]
mod tests {
    use super::BitSet;
    use bit_vec::BitVec;
    use std::cmp::Ordering::{Equal, Greater, Less};
    use std::vec::Vec;

    #[test]
    fn test_bit_set_show() {
        let mut s = BitSet::new();
        s.insert(1);
        s.insert(10);
        s.insert(50);
        s.insert(2);
        assert_eq!("{1, 2, 10, 50}", format!("{:?}", s));
    }

    #[test]
    fn test_bit_set_from_usizes() {
        let usizes = vec![0, 2, 2, 3];
        let a: BitSet = usizes.into_iter().collect();
        let mut b = BitSet::new();
        b.insert(0);
        b.insert(2);
        b.insert(3);
        assert_eq!(a, b);
    }

    #[test]
    fn test_bit_set_iterator() {
        let usizes = vec![0, 2, 2, 3];
        let bit_vec: BitSet = usizes.into_iter().collect();

        let idxs: Vec<_> = bit_vec.iter().collect();
        assert_eq!(idxs, [0, 2, 3]);

        let long: BitSet = (0..10000).filter(|&n| n % 2 == 0).collect();
        let real: Vec<_> = (0..10000 / 2).map(|x| x * 2).collect();

        let idxs: Vec<_> = long.iter().collect();
        assert_eq!(idxs, real);
    }

    #[test]
    fn test_bit_set_frombit_vec_init() {
        let bools = [true, false];
        let lengths = [10, 64, 100];
        for &b in &bools {
            for &l in &lengths {
                let bitset = BitSet::from_bit_vec(BitVec::from_elem(l, b));
                assert_eq!(bitset.contains(1), b);
                assert_eq!(bitset.contains(l - 1), b);
                assert!(!bitset.contains(l));
            }
        }
    }

    #[test]
    fn test_bit_vec_masking() {
        let b = BitVec::from_elem(140, true);
        let mut bs = BitSet::from_bit_vec(b);
        assert!(bs.contains(139));
        assert!(!bs.contains(140));
        assert!(bs.insert(150));
        assert!(!bs.contains(140));
        assert!(!bs.contains(149));
        assert!(bs.contains(150));
        assert!(!bs.contains(151));
    }

    #[test]
    fn test_bit_set_basic() {
        let mut b = BitSet::new();
        assert!(b.insert(3));
        assert!(!b.insert(3));
        assert!(b.contains(3));
        assert!(b.insert(4));
        assert!(!b.insert(4));
        assert!(b.contains(3));
        assert!(b.insert(400));
        assert!(!b.insert(400));
        assert!(b.contains(400));
        assert_eq!(b.len(), 3);
    }

    #[test]
    fn test_bit_set_intersection() {
        let mut a = BitSet::new();
        let mut b = BitSet::new();

        assert!(a.insert(11));
        assert!(a.insert(1));
        assert!(a.insert(3));
        assert!(a.insert(77));
        assert!(a.insert(103));
        assert!(a.insert(5));

        assert!(b.insert(2));
        assert!(b.insert(11));
        assert!(b.insert(77));
        assert!(b.insert(5));
        assert!(b.insert(3));

        let expected = [3, 5, 11, 77];
        let actual: Vec<_> = a.intersection(&b).collect();
        assert_eq!(actual, expected);
    }

    #[test]
    fn test_bit_set_difference() {
        let mut a = BitSet::new();
        let mut b = BitSet::new();

        assert!(a.insert(1));
        assert!(a.insert(3));
        assert!(a.insert(5));
        assert!(a.insert(200));
        assert!(a.insert(500));

        assert!(b.insert(3));
        assert!(b.insert(200));

        let expected = [1, 5, 500];
        let actual: Vec<_> = a.difference(&b).collect();
        assert_eq!(actual, expected);
    }

    #[test]
    fn test_bit_set_symmetric_difference() {
        let mut a = BitSet::new();
        let mut b = BitSet::new();

        assert!(a.insert(1));
        assert!(a.insert(3));
        assert!(a.insert(5));
        assert!(a.insert(9));
        assert!(a.insert(11));

        assert!(b.insert(3));
        assert!(b.insert(9));
        assert!(b.insert(14));
        assert!(b.insert(220));

        let expected = [1, 5, 11, 14, 220];
        let actual: Vec<_> = a.symmetric_difference(&b).collect();
        assert_eq!(actual, expected);
    }

    #[test]
    fn test_bit_set_union() {
        let mut a = BitSet::new();
        let mut b = BitSet::new();
        assert!(a.insert(1));
        assert!(a.insert(3));
        assert!(a.insert(5));
        assert!(a.insert(9));
        assert!(a.insert(11));
        assert!(a.insert(160));
        assert!(a.insert(19));
        assert!(a.insert(24));
        assert!(a.insert(200));

        assert!(b.insert(1));
        assert!(b.insert(5));
        assert!(b.insert(9));
        assert!(b.insert(13));
        assert!(b.insert(19));

        let expected = [1, 3, 5, 9, 11, 13, 19, 24, 160, 200];
        let actual: Vec<_> = a.union(&b).collect();
        assert_eq!(actual, expected);
    }

    #[test]
    fn test_bit_set_subset() {
        let mut set1 = BitSet::new();
        let mut set2 = BitSet::new();

        assert!(set1.is_subset(&set2)); //  {}  {}
        set2.insert(100);
        assert!(set1.is_subset(&set2)); //  {}  { 1 }
        set2.insert(200);
        assert!(set1.is_subset(&set2)); //  {}  { 1, 2 }
        set1.insert(200);
        assert!(set1.is_subset(&set2)); //  { 2 }  { 1, 2 }
        set1.insert(300);
        assert!(!set1.is_subset(&set2)); // { 2, 3 }  { 1, 2 }
        set2.insert(300);
        assert!(set1.is_subset(&set2)); // { 2, 3 }  { 1, 2, 3 }
        set2.insert(400);
        assert!(set1.is_subset(&set2)); // { 2, 3 }  { 1, 2, 3, 4 }
        set2.remove(100);
        assert!(set1.is_subset(&set2)); // { 2, 3 }  { 2, 3, 4 }
        set2.remove(300);
        assert!(!set1.is_subset(&set2)); // { 2, 3 }  { 2, 4 }
        set1.remove(300);
        assert!(set1.is_subset(&set2)); // { 2 }  { 2, 4 }
    }

    #[test]
    fn test_bit_set_is_disjoint() {
        let a = BitSet::from_bytes(&[0b10100010]);
        let b = BitSet::from_bytes(&[0b01000000]);
        let c = BitSet::new();
        let d = BitSet::from_bytes(&[0b00110000]);

        assert!(!a.is_disjoint(&d));
        assert!(!d.is_disjoint(&a));

        assert!(a.is_disjoint(&b));
        assert!(a.is_disjoint(&c));
        assert!(b.is_disjoint(&a));
        assert!(b.is_disjoint(&c));
        assert!(c.is_disjoint(&a));
        assert!(c.is_disjoint(&b));
    }

    #[test]
    fn test_bit_set_union_with() {
        //a should grow to include larger elements
        let mut a = BitSet::new();
        a.insert(0);
        let mut b = BitSet::new();
        b.insert(5);
        let expected = BitSet::from_bytes(&[0b10000100]);
        a.union_with(&b);
        assert_eq!(a, expected);

        // Standard
        let mut a = BitSet::from_bytes(&[0b10100010]);
        let mut b = BitSet::from_bytes(&[0b01100010]);
        let c = a.clone();
        a.union_with(&b);
        b.union_with(&c);
        assert_eq!(a.len(), 4);
        assert_eq!(b.len(), 4);
    }

    #[test]
    fn test_bit_set_intersect_with() {
        // Explicitly 0'ed bits
        let mut a = BitSet::from_bytes(&[0b10100010]);
        let mut b = BitSet::from_bytes(&[0b00000000]);
        let c = a.clone();
        a.intersect_with(&b);
        b.intersect_with(&c);
        assert!(a.is_empty());
        assert!(b.is_empty());

        // Uninitialized bits should behave like 0's
        let mut a = BitSet::from_bytes(&[0b10100010]);
        let mut b = BitSet::new();
        let c = a.clone();
        a.intersect_with(&b);
        b.intersect_with(&c);
        assert!(a.is_empty());
        assert!(b.is_empty());

        // Standard
        let mut a = BitSet::from_bytes(&[0b10100010]);
        let mut b = BitSet::from_bytes(&[0b01100010]);
        let c = a.clone();
        a.intersect_with(&b);
        b.intersect_with(&c);
        assert_eq!(a.len(), 2);
        assert_eq!(b.len(), 2);
    }

    #[test]
    fn test_bit_set_difference_with() {
        // Explicitly 0'ed bits
        let mut a = BitSet::from_bytes(&[0b00000000]);
        let b = BitSet::from_bytes(&[0b10100010]);
        a.difference_with(&b);
        assert!(a.is_empty());

        // Uninitialized bits should behave like 0's
        let mut a = BitSet::new();
        let b = BitSet::from_bytes(&[0b11111111]);
        a.difference_with(&b);
        assert!(a.is_empty());

        // Standard
        let mut a = BitSet::from_bytes(&[0b10100010]);
        let mut b = BitSet::from_bytes(&[0b01100010]);
        let c = a.clone();
        a.difference_with(&b);
        b.difference_with(&c);
        assert_eq!(a.len(), 1);
        assert_eq!(b.len(), 1);
    }

    #[test]
    fn test_bit_set_symmetric_difference_with() {
        //a should grow to include larger elements
        let mut a = BitSet::new();
        a.insert(0);
        a.insert(1);
        let mut b = BitSet::new();
        b.insert(1);
        b.insert(5);
        let expected = BitSet::from_bytes(&[0b10000100]);
        a.symmetric_difference_with(&b);
        assert_eq!(a, expected);

        let mut a = BitSet::from_bytes(&[0b10100010]);
        let b = BitSet::new();
        let c = a.clone();
        a.symmetric_difference_with(&b);
        assert_eq!(a, c);

        // Standard
        let mut a = BitSet::from_bytes(&[0b11100010]);
        let mut b = BitSet::from_bytes(&[0b01101010]);
        let c = a.clone();
        a.symmetric_difference_with(&b);
        b.symmetric_difference_with(&c);
        assert_eq!(a.len(), 2);
        assert_eq!(b.len(), 2);
    }

    #[test]
    fn test_bit_set_eq() {
        let a = BitSet::from_bytes(&[0b10100010]);
        let b = BitSet::from_bytes(&[0b00000000]);
        let c = BitSet::new();

        assert!(a == a);
        assert!(a != b);
        assert!(a != c);
        assert!(b == b);
        assert!(b == c);
        assert!(c == c);
    }

    #[test]
    fn test_bit_set_cmp() {
        let a = BitSet::from_bytes(&[0b10100010]);
        let b = BitSet::from_bytes(&[0b00000000]);
        let c = BitSet::new();

        assert_eq!(a.cmp(&b), Greater);
        assert_eq!(a.cmp(&c), Greater);
        assert_eq!(b.cmp(&a), Less);
        assert_eq!(b.cmp(&c), Equal);
        assert_eq!(c.cmp(&a), Less);
        assert_eq!(c.cmp(&b), Equal);
    }

    #[test]
    fn test_bit_set_shrink_to_fit_new() {
        // There was a strange bug where we refused to truncate to 0
        // and this would end up actually growing the array in a way
        // that (safely corrupted the state).
        let mut a = BitSet::new();
        assert_eq!(a.len(), 0);
        assert_eq!(a.capacity(), 0);
        a.shrink_to_fit();
        assert_eq!(a.len(), 0);
        assert_eq!(a.capacity(), 0);
        assert!(!a.contains(1));
        a.insert(3);
        assert!(a.contains(3));
        assert_eq!(a.len(), 1);
        assert!(a.capacity() > 0);
        a.shrink_to_fit();
        assert!(a.contains(3));
        assert_eq!(a.len(), 1);
        assert!(a.capacity() > 0);
    }

    #[test]
    fn test_bit_set_shrink_to_fit() {
        let mut a = BitSet::new();
        assert_eq!(a.len(), 0);
        assert_eq!(a.capacity(), 0);
        a.insert(259);
        a.insert(98);
        a.insert(3);
        assert_eq!(a.len(), 3);
        assert!(a.capacity() > 0);
        assert!(!a.contains(1));
        assert!(a.contains(259));
        assert!(a.contains(98));
        assert!(a.contains(3));

        a.shrink_to_fit();
        assert!(!a.contains(1));
        assert!(a.contains(259));
        assert!(a.contains(98));
        assert!(a.contains(3));
        assert_eq!(a.len(), 3);
        assert!(a.capacity() > 0);

        let old_cap = a.capacity();
        assert!(a.remove(259));
        a.shrink_to_fit();
        assert!(a.capacity() < old_cap, "{} {}", a.capacity(), old_cap);
        assert!(!a.contains(1));
        assert!(!a.contains(259));
        assert!(a.contains(98));
        assert!(a.contains(3));
        assert_eq!(a.len(), 2);

        let old_cap2 = a.capacity();
        a.clear();
        assert_eq!(a.capacity(), old_cap2);
        assert_eq!(a.len(), 0);
        assert!(!a.contains(1));
        assert!(!a.contains(259));
        assert!(!a.contains(98));
        assert!(!a.contains(3));

        a.insert(512);
        assert!(a.capacity() > 0);
        assert_eq!(a.len(), 1);
        assert!(a.contains(512));
        assert!(!a.contains(1));
        assert!(!a.contains(259));
        assert!(!a.contains(98));
        assert!(!a.contains(3));

        a.remove(512);
        a.shrink_to_fit();
        assert_eq!(a.capacity(), 0);
        assert_eq!(a.len(), 0);
        assert!(!a.contains(512));
        assert!(!a.contains(1));
        assert!(!a.contains(259));
        assert!(!a.contains(98));
        assert!(!a.contains(3));
        assert!(!a.contains(0));
    }

    #[test]
    fn test_bit_vec_remove() {
        let mut a = BitSet::new();

        assert!(a.insert(1));
        assert!(a.remove(1));

        assert!(a.insert(100));
        assert!(a.remove(100));

        assert!(a.insert(1000));
        assert!(a.remove(1000));
        a.shrink_to_fit();
    }

Test module: 24 #[test] functions covering construction, insertion, removal, iteration order, the four set operations (eq/cmp variants, in-place and not), is_subset/is_disjoint, shrink_to_fit (including the empty case the 0.5.3 release fixed), and clone. All assertions are deterministic; no proptest/quickcheck/fuzz harness is wired up. See FINDING-3 for the testing-confidence gap. Supports has-unit-tests.

src/lib.rs, line 753-833

    /// Returns the number of set bits in this set.
    #[inline]
    pub fn len(&self) -> usize {
        self.bit_vec
            .blocks()
            .fold(0, |acc, n| acc + n.count_ones() as usize)
    }

    /// Returns whether there are no bits set in this set
    #[inline]
    pub fn is_empty(&self) -> bool {
        self.bit_vec.none()
    }

    /// Clears all bits in this set
    #[inline]
    pub fn clear(&mut self) {
        self.bit_vec.clear();
    }

    /// Returns `true` if this set contains the specified integer.
    #[inline]
    pub fn contains(&self, value: usize) -> bool {
        let bit_vec = &self.bit_vec;
        value < bit_vec.len() && bit_vec[value]
    }

    /// Returns `true` if the set has no elements in common with `other`.
    /// This is equivalent to checking for an empty intersection.
    #[inline]
    pub fn is_disjoint(&self, other: &Self) -> bool {
        self.intersection(other).next().is_none()
    }

    /// Returns `true` if the set is a subset of another.
    #[inline]
    pub fn is_subset(&self, other: &Self) -> bool {
        let self_bit_vec = &self.bit_vec;
        let other_bit_vec = &other.bit_vec;
        let other_blocks = blocks_for_bits::<B>(other_bit_vec.len());

        // Check that `self` intersect `other` is self
        self_bit_vec.blocks().zip(other_bit_vec.blocks()).all(|(w1, w2)| w1 & w2 == w1) &&
        // Make sure if `self` has any more blocks than `other`, they're all 0
        self_bit_vec.blocks().skip(other_blocks).all(|w| w == B::zero())
    }

    /// Returns `true` if the set is a superset of another.
    #[inline]
    pub fn is_superset(&self, other: &Self) -> bool {
        other.is_subset(self)
    }

    /// Adds a value to the set. Returns `true` if the value was not already
    /// present in the set.
    pub fn insert(&mut self, value: usize) -> bool {
        if self.contains(value) {
            return false;
        }

        // Ensure we have enough space to hold the new element
        let len = self.bit_vec.len();
        if value >= len {
            self.bit_vec.grow(value - len + 1, false)
        }

        self.bit_vec.set(value, true);
        true
    }

    /// Removes a value from the set. Returns `true` if the value was
    /// present in the set.
    pub fn remove(&mut self, value: usize) -> bool {
        if !self.contains(value) {
            return false;
        }

        self.bit_vec.set(value, false);

        true
    }

Public mutation API (len, is_empty, clear, contains, is_disjoint, is_subset, is_superset, insert, remove). All bounds checks (value < bit_vec.len() in contains line 777, growth in insert line 815-817) are performed in safe Rust; no unsafe is needed in this layer. Supports datastructure-impl-safe.