rc_hashmap/
tokens.rs

1//! Lifetime-tied linear tokens and counting traits.
2//!
3//! Tokens are zero-sized proofs that a unit was acquired from a
4//! particular counter type. Dropping a token panics; the only valid
5//! way to dispose of it is to return it to the originating counter via
6//! `Count::put`.
7//!
8//! Goals
9//! - Provide a lifetime-driven API that forces balanced reference counting without relying on caller discipline.
10//! - Use zero-sized, linear tokens whose destruction panics unless they are returned to their originating counter.
11//! - Make ownership/liveness of map internals easy to reason about: entries keep their backing owner alive; user-facing refs are guaranteed tracked and released.
12//!
13//! Abstraction
14//! - Count: an object that mints and reclaims a unit “reference” by returning a `Token` and later accepting it back. `Count` is the sole place where increments and decrements occur.
15//! - Token: a zero-sized, non-cloneable proof that one unit was acquired. It is lifetime-bound and its `Drop` panics to catch unbalanced flows. The only valid disposal is passing it to `Count::put`.
16//!
17//! Why the type system helps
18//! - Origin binding (type-level only): `Token<'a, C>` uses two markers to separate lifetime from the counter type: `PhantomData<&'a ()>` tracks the lifetime, and `PhantomData<*const C>` brands the token to the counter type without requiring `C: 'a`.
19//! - Branding is to the counter type, not to a specific counter instance; it does not prevent returning a token to a different instance of the same counter type. Higher-level APIs are responsible for pairing tokens with their originating instance.
20//! - Linearity and balance: `Token` does not implement `Copy` or `Clone`, so it cannot be duplicated. Each `get` yields exactly one `Token` that must be consumed by exactly one `put`. Dropping a `Token` instead of returning it panics, catching unbalanced flows.
21//! - Zero cost: Tokens are ZSTs; they add no runtime footprint and no allocation. The only costs are the underlying counter operations.
22//!
23//! Unwinding and Drop panics
24//! - Panicking in `Token::drop` during another unwind aborts. Tokens are an internal mechanism, so this fail-fast behavior is acceptable for this crate.
25//!
26//! Patterns
27//! - Owned-token pattern: When a function owns the token and can consume it by value (i.e., not in a `Drop` impl), prefer moving the token directly into `Count::put` without `ManuallyDrop`.
28//!   For example, `CountedHandle` owns `Token<'_, UsizeCount>`. `CountedHashMap::put(self, handle)` consumes `handle`, moves out its token by value, and calls `entry.refcount.put(token)`.
29//! - Branch-free Drop with `ManuallyDrop`: If the token must be held inside a type that implements `Drop` and the token isn’t owned by value at drop time, store it in `core::mem::ManuallyDrop<Token<...>>` and move it out in `Drop` via `ManuallyDrop::take` to avoid implicit drops and extra branches.
30//!
31//! Owned + destructuring example
32//! ```rust
33//! use rc_hashmap::tokens::{Count, Token, UsizeCount};
34//!
35//! // A handle-like wrapper that owns a linear token.
36//! struct MyHandle<'a> {
37//!     token: Token<'a, UsizeCount>,
38//!     counter: &'a UsizeCount,
39//! }
40//!
41//! // By taking `MyHandle` by value, we own its fields. Destructure to move
42//! // the token out by value and return it directly — no Drop, no ManuallyDrop.
43//! fn release(MyHandle { token, counter }: MyHandle<'_>) -> bool {
44//!     counter.put(token)
45//! }
46//! ```
47//!
48//! Holder pattern with ManuallyDrop
49//! ```rust
50//! use core::mem::ManuallyDrop;
51//! use rc_hashmap::tokens::{Count, Token, UsizeCount};
52//!
53//! struct Holder<'a> {
54//!     counter: &'a UsizeCount,
55//!     token: ManuallyDrop<Token<'a, UsizeCount>>,
56//! }
57//!
58//! impl<'a> Holder<'a> {
59//!     fn new(counter: &'a UsizeCount) -> Self {
60//!         // `get()` returns a `'static` token which coerces to `'a` for `Token`.
61//!         Self { counter, token: ManuallyDrop::new(counter.get()) }
62//!     }
63//! }
64//!
65//! impl<'a> Drop for Holder<'a> {
66//!     fn drop(&mut self) {
67//!         // SAFETY: We never let `token` be dropped implicitly; move it out exactly once.
68//!         let t = unsafe { ManuallyDrop::take(&mut self.token) };
69//!         let _ = self.counter.put(t);
70//!     }
71//! }
72//! ```
73//!
74//! Using the pieces together
75//! - Keep the owner alive while any entry exists: `RcHashMap` stores a keepalive `Token<'static, RcCount<Inner>>` per entry. The token is minted from the map’s `RcCount` on insert and returned when the last user-facing `Ref` to that entry is dropped, ensuring the backing allocation outlives the entry.
76//! - Ensure every user Ref is counted and released: `Ref` owns a `CountedHandle` which carries a `Token<'_, UsizeCount>` for the entry’s local refcount. Cloning a `Ref` mints a new token; dropping a `Ref` returns its token. When the per-entry count reaches zero, the entry is unlinked and dropped, then the keepalive token is returned to decrement the owner strong count.
77//!
78//! Implementation variants
79//! - UsizeCount: single-threaded counter using `Cell<usize>` to track outstanding user-facing references to an entry. Increment uses `wrapping_add` and aborts on wrap to 0 (matching `Rc`). Decrement asserts nonzero before subtracting. An `is_zero()` helper checks whether the current count is zero.
80//! - RcCount<T>: encapsulates raw `Rc` strong-count inc/dec behind the `Count` interface. Unsafety is internal; callers only manipulate `Token`s. Construct via `RcCount::new(&rc)` or `RcCount::from_weak(&weak)`.
81//!
82//! Notes
83//! - Observing zero: `UsizeCount::put` returns a bool indicating whether the count reached zero. `RcCount::put` returns true iff the strong count was 1 before the decrement (typically false when the map itself also holds a strong `Rc`).
84//! - Single-threaded only: `UsizeCount` is not `Sync`, and `RcCount` inherits `Rc`’s `!Send + !Sync` semantics.
85//! - Overflow behavior (same as Rc): `UsizeCount::get` performs `wrapping_add(1)`, stores it, then aborts the process if the result is 0.
86//! - Debug-only behavior: `RcCount::{get,put}` include debug assertions on liveness via `Weak::strong_count()`. These checks are compiled out in release builds.
87//!
88//! Alternatives considered
89//! - Plain `usize` counts without tokens: relies on discipline and is easy to misuse (double `put`, missing `put` on early return). Tokens significantly reduce misuse by construction, but do not enforce per-instance branding.
90//! - Storing a runtime back-pointer in the token: not implemented. Without per-instance branding, cross-instance misuse is technically possible; in this crate we rely on API structure to maintain correct pairing.
91
92use core::cell::Cell;
93use core::marker::PhantomData;
94use std::rc::{Rc, Weak};
95
96/// Zero-sized, linear token tied to its originating counter via lifetime.
97pub struct Token<'a, C: ?Sized> {
98    // Lifetime is tracked separately from the counter type to avoid
99    // imposing `'a` bounds on `C` (useful for generic counters).
100    _lt: PhantomData<&'a ()>,
101    _ctr: PhantomData<*const C>,
102}
103
104impl<'a, C: ?Sized> Token<'a, C> {
105    #[inline]
106    pub(crate) fn new() -> Self {
107        Self {
108            _lt: PhantomData,
109            _ctr: PhantomData,
110        }
111    }
112}
113
114impl<'a, C: ?Sized> Drop for Token<'a, C> {
115    fn drop(&mut self) {
116        // Intentional fail-fast on misuse: token must be consumed by Count::put.
117        panic!("Token dropped without Count::put");
118    }
119}
120
121/// A source of counted references, enforced by linear Token flow.
122pub trait Count {
123    /// The token type minted by this counter.
124    type Token<'a>: Sized
125    where
126        Self: 'a;
127
128    /// Acquire one counted reference and return a linear token for it.
129    ///
130    /// We mint tokens with a 'static lifetime parameter. The token itself is
131    /// still branded to this counter via its type parameter, and can be
132    /// covariantly shortened when returning it via `put`.
133    fn get(&self) -> Self::Token<'static>;
134
135    /// Return (consume) a previously acquired token.
136    /// Returns true if the count is now zero.
137    fn put<'a>(&'a self, t: Self::Token<'a>) -> bool;
138}
139
140/// Single-threaded reference counter for entries.
141#[derive(Debug)]
142pub struct UsizeCount {
143    count: Cell<usize>,
144}
145
146impl UsizeCount {
147    pub fn new(initial: usize) -> Self {
148        Self {
149            count: Cell::new(initial),
150        }
151    }
152
153    /// Returns true if the current count is zero.
154    #[inline]
155    pub fn is_zero(&self) -> bool {
156        self.count.get() == 0
157    }
158}
159
160impl Count for UsizeCount {
161    type Token<'a>
162        = Token<'a, Self>
163    where
164        Self: 'a;
165
166    #[inline]
167    fn get(&self) -> Self::Token<'static> {
168        let c = self.count.get();
169        let n = c.wrapping_add(1);
170        self.count.set(n);
171        if n == 0 {
172            // Follow Rc semantics: abort on overflow rather than continue unsafely.
173            std::process::abort();
174        }
175        Token::<'static, Self>::new()
176    }
177
178    #[inline]
179    fn put<'a>(&'a self, t: Self::Token<'a>) -> bool {
180        let c = self.count.get();
181        assert!(c > 0, "UsizeCount underflow");
182        let n = c - 1;
183        self.count.set(n);
184        core::mem::forget(t);
185        n == 0
186    }
187}
188
189/// Rc-backed manual counter. Uses raw-pointer strong count manipulation.
190pub struct RcCount<T> {
191    ptr: *const T,
192    weak: Weak<T>,
193    _nosend: PhantomData<*mut ()>,
194}
195
196impl<T> RcCount<T> {
197    pub fn new(rc: &Rc<T>) -> Self {
198        let weak = Rc::downgrade(rc);
199        let raw = Rc::into_raw(rc.clone());
200        unsafe { Rc::decrement_strong_count(raw) };
201        Self {
202            ptr: raw,
203            weak,
204            _nosend: PhantomData,
205        }
206    }
207
208    pub fn from_weak(weak: &Weak<T>) -> Self {
209        let raw = weak.as_ptr();
210        Self {
211            ptr: raw,
212            weak: weak.clone(),
213            _nosend: PhantomData,
214        }
215    }
216}
217
218impl<T: 'static> Count for RcCount<T> {
219    type Token<'a>
220        = Token<'a, Self>
221    where
222        Self: 'a;
223
224    #[inline]
225    fn get(&self) -> Self::Token<'static> {
226        debug_assert!(self.weak.strong_count() > 0);
227        unsafe { Rc::increment_strong_count(self.ptr) };
228        Token::<'static, Self>::new()
229    }
230
231    #[inline]
232    fn put<'a>(&'a self, t: Self::Token<'a>) -> bool {
233        debug_assert!(self.weak.strong_count() > 0);
234        let was_one = self.weak.strong_count() == 1;
235        unsafe { Rc::decrement_strong_count(self.ptr) };
236        core::mem::forget(t);
237        was_one
238    }
239}
240
241#[cfg(test)]
242mod tests {
243    use super::*;
244    use proptest::prelude::*;
245
246    #[test]
247    /// Invariant: Dropping a token without returning it via `Count::put`
248    /// panics (fail-fast). This ensures linear token flow is enforced.
249    fn token_drop_panics() {
250        let c = UsizeCount::new(0);
251        let t = c.get();
252        let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| drop(t)));
253        assert!(res.is_err());
254    }
255
256    #[test]
257    /// Invariant: `UsizeCount` reflects the exact number of outstanding
258    /// tokens, and `put` returns true when the count reaches zero.
259    fn usizecount_balance_and_zero() {
260        let c = UsizeCount::new(0);
261        let t1 = c.get();
262        let t2 = c.get();
263        assert!(!c.is_zero());
264        assert!(!c.put(t1));
265        assert!(c.put(t2));
266        assert!(c.is_zero());
267    }
268
269    #[test]
270    /// Invariant: `RcCount` increments the underlying `Rc` strong count on
271    /// `get` and decrements it on `put`. For a live `Rc`, `put` never
272    /// indicates the count was one (because the map holds another strong ref
273    /// in typical usage).
274    fn rccount_increments_and_put_flag() {
275        let rc = Rc::new(123);
276        let weak = Rc::downgrade(&rc);
277        let c = RcCount::new(&rc);
278        let before = weak.strong_count();
279        let t = c.get();
280        assert_eq!(weak.strong_count(), before + 1);
281        let was_one = c.put(t);
282        assert!(!was_one);
283        assert_eq!(weak.strong_count(), before);
284    }
285
286    // Property: For arbitrary get/put sequences, `UsizeCount`'s zero/non-zero
287    // state matches whether there are tokens outstanding, and `put`'s return
288    // value signals transition-to-zero exactly when the last token is returned.
289    proptest! {
290        #[test]
291        fn prop_usizecount_get_put_balance(ops in proptest::collection::vec(0u8..=1, 0..200)) {
292            let c = UsizeCount::new(0);
293            let mut toks: Vec<Token<'static, UsizeCount>> = Vec::new();
294            for op in ops.iter().copied() {
295                match op {
296                    0 => {
297                        toks.push(c.get());
298                        assert!(!c.is_zero());
299                    }
300                    _ => {
301                        if let Some(t) = toks.pop() {
302                            let now_zero = c.put(t);
303                            assert_eq!(now_zero, toks.is_empty());
304                            assert_eq!(c.is_zero(), toks.is_empty());
305                        }
306                    }
307                }
308            }
309            assert_eq!(c.is_zero(), toks.is_empty());
310            while let Some(t) = toks.pop() {
311                let now_zero = c.put(t);
312                assert_eq!(now_zero, toks.is_empty());
313            }
314            assert!(c.is_zero());
315        }
316
317        // Property: Two `UsizeCount` instances are independent; operations on
318        // one must not affect the other's zero/non-zero state or put results.
319        #[test]
320        fn prop_two_usizecounts_independent(ops in proptest::collection::vec((0u8..=1, 0u8..=1), 0..200)) {
321            let a = UsizeCount::new(0);
322            let b = UsizeCount::new(0);
323            let mut ta: Vec<Token<'static, UsizeCount>> = Vec::new();
324            let mut tb: Vec<Token<'static, UsizeCount>> = Vec::new();
325            for (which, op) in ops.into_iter() {
326                match (which, op) {
327                    (0, 0) => { ta.push(a.get()); }
328                    (0, 1) => {
329                        if let Some(t) = ta.pop() {
330                            let now_zero = a.put(t);
331                            assert_eq!(now_zero, ta.is_empty());
332                        }
333                    }
334                    (1, 0) => { tb.push(b.get()); }
335                    (1, 1) => {
336                        if let Some(t) = tb.pop() {
337                            let now_zero = b.put(t);
338                            assert_eq!(now_zero, tb.is_empty());
339                        }
340                    }
341                    _ => unreachable!(),
342                }
343                assert_eq!(a.is_zero(), ta.is_empty());
344                assert_eq!(b.is_zero(), tb.is_empty());
345            }
346
347            while let Some(t) = ta.pop() { let _ = a.put(t); }
348            while let Some(t) = tb.pop() { let _ = b.put(t); }
349            assert!(a.is_zero());
350            assert!(b.is_zero());
351        }
352
353        // Property: `RcCount` round-trip increments and decrements the strong
354        // count by the exact number of outstanding tokens. Returning tokens
355        // never reports `was_one` here because another owner (the map) holds
356        // a strong reference; the weak strong_count tracks changes.
357        #[test]
358        fn prop_rccount_roundtrip(n in 0usize..100) {
359            let rc = Rc::new(());
360            let weak = Rc::downgrade(&rc);
361            let c = RcCount::new(&rc);
362            let before = weak.strong_count();
363            let mut toks: Vec<Token<'static, RcCount<_>>> = Vec::new();
364            for _ in 0..n { toks.push(c.get()); }
365            assert_eq!(weak.strong_count(), before + n);
366            while let Some(t) = toks.pop() {
367                let was_one = c.put(t);
368                assert!(!was_one);
369                assert_eq!(weak.strong_count(), before + toks.len());
370            }
371            assert_eq!(weak.strong_count(), before);
372        }
373    }
374}