A no_std, alloc-backed small-vector whose inline/heap state is encoded in the highest bit of the length field, plus an optional three-state copy-on-write variant.
use covariant_vec::CovariantVec;
let mut v = CovariantVec::<i32, 4>::new();
v.extend(1..=4);
assert!(!v.is_heap()); // still inline
v.push(5); // spills to the heap on the 5th element
assert!(v.is_heap());
assert_eq!(&*v, &[1, 2, 3, 4, 5]);- Length-encoded state tag. No data-embedded sentinel, no extra discriminant word.
len's top bit isHEAP_TAG;CovariantVec<u64, 4>is exactly 40 bytes (N·S + W). - Covariant in both
'aandT. The'aparameter is a variance anchor (PhantomData<&'a ()>), soCovariantVec<'src, Label<'src>, N>is the intended shape; variance is pinned at compile time intests/variance.rs. - Lenient dropck (nightly). With the
dropck_eyepatchfeature,#[may_dangle]is applied to both'aandT, so a container can be dropped after its lifetime has expired. - Fallible allocation everywhere.
try_reserve/try_reserve_exact/try_to_mutreturnTryReserveErroron capacity overflow orallocfailure, without unstable APIs. - Optional copy-on-write (
cow).CovariantCow<'a, T, N>adds a zero-copyBorrowedstate by spending bit 62 of the length word, with owned-only_ownedmethods so!Clonetypes remain usable when the cow is known to be owned. - Nightly
T: Copyfast paths (specialization).Clone,to_mut/try_to_mut, and the fallible materializers switch to a singleptr::copy_nonoverlappingforT: Copy— same code path, same panic safety, same observable behavior. Stable builds always take the element-wise loop.
| Feature | Nightly? | Effect |
|---|---|---|
| (default) | — | Inline + heap two-state storage, element-wise clone loops. |
cow |
No | Enables the three-state CovariantCow. |
dropck_eyepatch |
Yes | #[may_dangle] on Drop impls (RFC 1238). |
specialization |
Yes | T: Copy bulk-copy fast paths. Minimum known-good nightly: nightly-2026-01-16. |
Rust 1.80. The floor is set by bare size_of/align_of in the prelude (1.80) and inline const {} blocks (1.79). The core::error::Error impl for TryReserveError is gated on a build.rs-emitted cfg for rustc ≥ 1.81; older compilers simply don't get the impl.
For a zero-sized T, no bytes are ever stored; the count is the ownership. Three conventions must hold together:
push/push_ownedmem::forgetthe value and increment the length — the count now owns its one conceptual destructor.drop_contents/truncate/retain_mut/Drain::droprundrop_in_placeoverraw_lenunwritten slots — one destructor per counted element.pop/IntoIter::next/Drain::nextptr::reada value out of such an unwritten slot and decrement the count — the element is not dropped twice.
Every site is annotated in the source; a change that breaks the balance is a change that either double-counts or double-drops.
append, split_off, splice and extract_if are provided natively over the two-state encoding — no Vec round-trip. An inline-to-inline append that fits allocates nothing; split_off state-normalizes its result like Clone; splice refills the drained region's dead slots in place and writes a longer replacement in geometrically growing chunks (one tail shift for an exact size hint); extract_if shares retain_mut's backshift scan and publishes an empty length for the scan (forget → leak, never double-drop). extend_from_slice is the stable T: Copy bulk append: one memcpy, one length publication, and on a borrowed cow it fuses materialization with the reservation. sort*/dedup* remain reachable through DerefMut.
- A stable
T: Copyspecialization of the clone loops — specialization is the only mechanism that keys onT: Copyexactly, and it is nightly-only. (extend_from_sliceis the stable bulk-copy entry point; it takes&[T], so it needs no specialization.) - An
Optionniche — the tag lives inside a plainusize, whose every bit pattern is valid; rustc cannot see "bit 63 set ⇒ heap".Option<CovariantVec>pays the full discriminant word.
With the cow feature, CovariantCow<'a, T, N> adds a zero-copy borrowed state:
use covariant_vec::CovariantCow;
let data = [1, 2, 3, 4, 5];
let mut c = CovariantCow::<i32, 4>::from_borrowed(&data);
assert!(c.is_borrowed()); // zero-copy
c.push(6); // copy-on-write: materializes, then appends
assert_eq!(&*c, &[1, 2, 3, 4, 5, 6]);
assert_eq!(data, [1, 2, 3, 4, 5]); // borrower untouchedBorrowed-aware methods carry a T: Clone bound (materialization requires it); each has an _owned twin that instead panics on a borrowed cow, so an owned !Clone cow can still mutate in every way that does not clone.
cargo test --all-features # nightly: dropck_eyepatch + specialization + cow
cargo test --features cow # stable: cow only
cargo test --no-default-features
cargo miri test --all-featuresCI additionally runs Miri under both Stacked Borrows and Tree Borrows, AddressSanitizer + LeakSanitizer, clippy, and rustdoc.
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.