Small vector
rusty-js-smallvec is a small-vector type that keeps a handful of elements inline and spills to the heap only when the list grows, sparing an allocation for collections that are almost always tiny. It implements just the methods Cruft's hidden-class store uses, and contains no unsafe code.
For a property bag, a set of shape transitions, or a list of watchers that is almost always tiny, allocating a Vec for two elements is a heap trip that performance-sensitive code avoids by keeping the first few elements inline and spilling to the heap only when the list actually grows. That is what the crates.io smallvec crate provides, and Cruft's hidden-class store (shapes) used it for exactly that.
Cruft ships its own stand-in for smallvec, rusty-js-smallvec. The one distinction that matters: smallvec is the canonical example of unsafe gone wrong, and Cruft's replacement carries none of it.
Alpha.rusty-js-smallvec(Cruft 0.0.10) is a zero-dependency stand-in for the crates.iosmallveccrate, covering only the small surface the shapes layer consumes. It has not been externally audited. Do not rely on it in production or against adversarial input.
What the crate implements
The crate does not reproduce all of smallvec. The crates.io crate is a full inline vector: insert, remove, retain, drain, extend, spare-capacity APIs, a union storage optimization, serde and write feature flags. Cruft's version implements new, push, len, is_empty, iter (and iter().find(..)), clone, and both by-reference and by-value IntoIterator. That is the whole public surface, and it is deliberate: Cruft implements only what a consumer actually calls. The consumer is the shapes layer, which touches inline vectors through that handful of methods and no others.
So the crate is the exact slice of inline-vector behavior the shapes layer calls, and only that.
The real seam is the outer enum
In the shapes layer the true abstraction is an enum the engine defines called SmallOrLarge: a Small arm holding inline storage, and a Large arm holding a Vec plus a HashMap, with a one-way promotion from Small to Large the moment the collection would exceed its inline capacity (INLINE_CAP + 1).
The crate sits inside the Small arm as a swappable storage detail. Because the wrapper promotes to Large at INLINE_CAP + 1, the inline vector's own heap-spill path is never taken in the shapes usage; the wrapper spills first. The crate implements and tests its spill path anyway, so it is a correct general-purpose type, but in production that spill branch is dead code guarded by the outer enum.
That is why comparing it head-to-head with smallvec is partly the wrong frame. The maturity smallvec earns from years of adversarial use lives at the storage-detail layer; the layer that actually decides Cruft's memory behavior is the engine's own enum, which is separate code with its own tests.
The classic smallvec bug family is eliminated by construction
smallvec is fast because it stores inline elements in a union of MaybeUninit memory and tracks, out of band, how many slots are actually initialized. That design is why smallvec has a real CVE history: the crate has shipped unsafe soundness bugs where the length and the initialized region disagreed, letting code read or drop uninitialized or already-moved memory. The whole family comes from one source, unsafe promising the compiler that a slot is initialized when the length bookkeeping is wrong.
Cruft's version does not make that promise. It stores inline elements as [Option<T>; N] with a len field, so every slot carries its own Some/None tag (Inline { buf: [Option<T>; N], len }). There is no MaybeUninit, no union, and the source contains zero occurrences of the unsafe keyword. The invariant "buf[..len] are Some, the tail is None" is enforced by the type system, not by an out-of-band length that unsafe code trusts. If the bookkeeping is ever wrong, the failure mode is a None where the code .expect()ed a Some: a clean panic, not undefined behavior.
The cost is small: one tag byte (one niche or discriminant) per inline slot, at the small N the shapes layer uses (SLOTS_INLINE_CAP = 8, TRANSITIONS_INLINE_CAP = 4). This is a deliberate trade of a per-slot tag byte for safety and correctness of the property store. A MaybeUninit variant was considered and rejected.
The spill path is where an inline vector would most plausibly duplicate or drop an element. push at capacity drains each inline slot with slot.take() (leaving None behind) into a freshly sized Vec, then pushes the new value and swaps the whole enum to Heap. Each element moves exactly once. Tests cover this for Copy and non-Copy (String) element types, for order preservation across the spill, and for clone independence between inline and spilled copies.
Test coverage
The unit tests cover: empty/new is inline; push stays inline up to exactly N; push at N+1 spills to heap with no duplication or loss; spill preserves non-Copy elements; the iter().find lookup the shapes get() uses; clone independence across the inline/heap boundary; and by-value IntoIterator draining for both inline and heap variants. The integration gate is the shapes consumer suite.
There are no fuzz tests, no Miri run, and no property-based tests over random push/spill sequences. The zero-unsafe design does not need Miri the way smallvec's does, since there is no unsafe for it to catch, but the absence is worth stating.
Limitations
- It is a partial type by design. No
insert,remove,retain,drain, indexing,extend, serde, or the union storage optimization. If a future consumer needs any of those, this crate does not have them and must grow, with new tests, before it can be trusted at that surface. The crates.iosmallvecwins the moment you need anything outside the consumed surface. - The maturity baseline is real.
smallvechas years of adversarial production use and a public bug-and-fix history that hardened it. Cruft's version has a single test module and one internal consumer. Its correctness rests on being small and safe by construction, not on having survived the world. - The spill path is dead code in production. The outer
SmallOrLargeenum promotes toLargebefore the inline vector would ever spill, so the spill branch, though tested, does not run in the shapes usage. Its correctness is covered by unit test, not exercised by the live workload. - No fuzzing or Miri. The zero-
unsafedesign removes the category of bug Miri exists to find, but no property or fuzz coverage over random operation sequences exists today.