RawVec and Zero-Sized Types

🧪 Chapter 9 — Testing & Benchmarking Your Vec

“Trust, but verify — with property tests, sanitizers, and time.”

9.1 unit tests: the obvious edge cases

#[test]
fn push_pop_roundtrip() {
    let mut v = MyVec::new();
    for i in 0..10_000 { v.push(i); }
    for i in (0..10_000).rev() { assert_eq!(v.pop(), Some(i)); }
    assert_eq!(v.pop(), None);
}

#[test]
fn insert_remove_middle() {
    let mut v = MyVec::new();
    for i in 0..10 { v.push(i); }
    v.insert(5, 99);
    assert_eq!(v.get(5), Some(&99));
    assert_eq!(v.remove(5), 99);
    assert_eq!(v.as_slice(), &[0,1,2,3,4,5,6,7,8,9]);
}

#[test]
fn drop_complex() {
    use std::cell::Cell;
    struct D<'a>(&'a Cell<i32>);
    impl<'a> Drop for D<'a> { fn drop(&mut self){ self.0.set(self.0.get()+1); } }
    let c = Cell::new(0);
    {
        let mut v = MyVec::new();
        for _ in 0..123 { v.push(D(&c)); }
        // clear triggers 123 drops
        v.clear();
        // re-use after clear
        for _ in 0..7 { v.push(D(&c)); }
    } // drop remaining 7
    assert_eq!(c.get(), 130);
}

9.2 property tests (proptest/quickcheck)

randomized invariants catch corner cases:

  • len <= cap always
  • after extend + clear, len == 0 and further push works
  • collect::<MyVec<_>>() matches std::Vec<_>() for same sequence

example (using proptest):

use proptest::prelude::*;

proptest! {
  #[test]
  fn prop_push_matches_std(xs: Vec<i32>) {
      let mut a = MyVec::new();
      let mut b = Vec::new();
      for x in xs {
          a.push(x);
          b.push(x);
      }
      prop_assert_eq!(a.as_slice(), &b[..]);
  }
}

9.3 UB/bounds checking with Miri

Miri interprets Rust and screams on UB (use-after-free, out-of-bounds, invalid drops).

cargo +nightly miri test

fix anything it complains about — miri is your best friend for unsafe code.


9.4 fuzzing (cargo-fuzz)

wrap public ops (push, insert, remove, clear, into_iter) with a libFuzzer harness and throw random sequences at them. compare behavior against std::Vec as an oracle.


9.5 performance benchmarking (criterion)

microbench push, extend, insert/remove, iteration.

use criterion::{criterion_group, criterion_main, Criterion, black_box};

fn bench_push(c: &mut Criterion) {
    c.bench_function("myvec push 1e6", |b| {
        b.iter(|| {
            let mut v = MyVec::new();
            for i in 0..1_000_000 { v.push(black_box(i)); }
            black_box(v.len())
        })
    });
}

criterion_group!(benches, bench_push);
criterion_main!(benches);

💡 check growth policy: doubling vs 1.5× can change cache behavior & realloc cost.


9.6 sanitizer & valgrind notes

  • address/thread sanitizers are less targeted than miri for UB, but can catch wild writes in FFI.
  • valgrind can spot leaks if you accidentally skip a drop path.

9.7 summary

  • unit + property tests ensure correctness on paths you didn’t think of.
  • miri hunts UB; fuzzing hunts pathological sequences.
  • criterion keeps you honest about perf regressions.