pub unsafe trait StackStorage<const INDEX_BITS: u32> {
// Required methods
unsafe fn head(&self) -> &StackHead<INDEX_BITS>;
unsafe fn load_next(&self, index: u32) -> u32;
unsafe fn store_next(&self, index: u32, next: u32);
}Expand description
One implementor supplies a StackHead and atomic per-index links. The
head↔links binding is fixed by the implementor; links may be slot-resident
or fused in ArrayIndexStack. The implementation must be non-blocking if
the resulting StackOps operations are to remain lock-free.
§Safety
Implementing this trait is a soundness commitment. The implementor must:
- Bind each head to exactly one live implementor and one link backing for its whole life; never share or rebind it while an index is reachable.
- Use one stable index↔cell mapping. After an
Acquireobservation of a head published by aReleasepush,load_nextmust observe that push’sstore_nextor a later write in the cell’s modification order, never an earlier write. A link cell may be mutated only by the stack-algorithm push that is about to publish its index through the binding currently receiving a valid, unique publish/recycle authority. That authority may be freshly issued or legitimately transferred by a successful pop from another binding sharing the cells. Later legitimate pop+repush operations may therefore write the same cell again. Out-of-band storage-owner, payload, direct, or forged writes are forbidden, even when they leave an acyclic, in-range chain. - Keep reachable index populations disjoint across bindings sharing link cells. Sharing cells with disjoint populations is allowed, and a popped index may be transferred from one binding to another before the receiving binding publishes it.
- Return only
TAILor a valid index from a dedicated, non-payload- aliased link cell. - Return the same logical head from
headon every call. - Document a fixed link domain, a subset of
0 .. INDEX_MASK, with a dedicated cell for every member. Hooks must be memory-safe in that domain; unchecked access outside it is permitted only because the caller contract guarantees the algorithm never supplies such an index. - Make every link-cell access atomic; a stale popper may read while a concurrent push writes and then lose its head CAS.
§Shared-storage hazard class: detection boundary
The inventory counts binding relationships, not storage values. Shared cells are valid when reachable populations stay disjoint; a successful pop may transfer an index’s authority to another binding, which may then publish it. The forbidden shapes are:
| Shape | Forbidden arrangement | Detector |
|---|---|---|
| 1 | One binding reads and writes different backings. | May eventually trip the self-loop guard. |
| 2 | Two live bindings share one head but use different backings. | May trip the self-loop guard; not structural. |
| 3 | Bindings share cells while an index is reachable from both. | Can remain acyclic and silently double-issue. |
| 4 | A live head is rebound over time to a different backing. | Can leak first, then trip the self-loop guard. |
These are implementor obligations, not a complete runtime detector; direct forged writes and deeper acyclic corruption can pass every guard.
§Ordering contract
load_next must use Acquire (or stronger), and store_next must use
Release (or stronger). Head publication already carries the link’s
visibility, but these link orderings are deliberate defence-in-depth and
keep an implementation independent of the stack’s internal head orderings;
see docs/perf/TIS_LINK_ORDERING_WEAK_CAS_GATE.md for the measured status.
The three hooks are unsafe fn: the compiler requires an unsafe call site.
Their caller-side contracts are stated on the methods below; the
implementation obligations above remain the unsafe impl’s responsibility.
The complete design rationale is in
docs/adr/2026-09-01-tagged-index-stack-storage-binding-closure.md.
§Stability
This trait is intentionally open for external slot-resident implementations; future methods will have default bodies or require a major release.
Required Methods§
Sourceunsafe fn head(&self) -> &StackHead<INDEX_BITS>
unsafe fn head(&self) -> &StackHead<INDEX_BITS>
The stack’s head word.
§Safety
The caller may use the returned reference only as this binding’s head and must not create a competing head↔links binding around it.
Sourceunsafe fn load_next(&self, index: u32) -> u32
unsafe fn load_next(&self, index: u32) -> u32
Load index’s next link with Acquire ordering.
§Safety
index must have been pushed through this exact binding at least once,
so its link cell was initialized by store_next. It need not remain
reachable: a concurrent popper may win before this caller’s CAS.
Sourceunsafe fn store_next(&self, index: u32, next: u32)
unsafe fn store_next(&self, index: u32, next: u32)
Store index’s next link with Release ordering. This is the only
stack write to link storage, and it is lazy: links are written only
during a push immediately before that push’s publishing CAS.
§Safety
The caller must be in the CAS-valid push phase: index satisfies
StackOps::push_index’s three caller obligations, including a unique
authority legitimately transferred from any binding sharing the cells;
next is TAIL or the index observed as this binding’s head; and this
call is made by the stack algorithm immediately before the CAS that
publishes index. No storage-owner, payload, direct, or forged write
may substitute for this call.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".