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}