sefer_region/handle.rs
1//! [`Handle`] — the typed, copyable reference to a value in a [`Region`].
2//!
3//! [`Region`]: crate::Region
4
5use core::marker::PhantomData;
6use core::num::NonZeroUsize;
7
8/// An opaque, copyable reference to a value stored in a [`Region`].
9///
10/// A handle wraps a `slotmap::DefaultKey` (an index plus a generation) and a
11/// `region_id` that identifies which `Region` instance the handle belongs to.
12/// It is `Copy` and unconditionally `Send + Sync` regardless of `T` — it owns
13/// no `T`, it only names one. The `PhantomData<fn() -> T>` keeps the handle
14/// *typed* (so a `Handle<A>` cannot be passed to a `Region<B>`) while staying
15/// covariant in `T` and free of any drop/auto-trait obligations.
16///
17/// The `region_id: NonZeroUsize` field ensures that handles from different
18/// `Region` instances never collide even if they have the same `key`. Using
19/// `NonZeroUsize` preserves the niche optimization for `Option<Handle<T>>`.
20/// `region_id` is pointer-width (not a fixed 64 bits) so this type stays
21/// buildable on no_std targets without 64-bit atomics (e.g.
22/// `thumbv7em-none-eabi`, `i686-*`) — every such target
23/// still has pointer-width atomics.
24///
25/// ## Layout is an observed property, not a guarantee
26///
27/// This crate does not use `#[repr(C)]`/`#[repr(transparent)]` here — there is
28/// no FFI or C-ABI use case for `Handle<T>`, and one would be misleading
29/// regardless: the inner `slotmap::DefaultKey` is itself not `#[repr(C)]`
30/// upstream, so pinning only the outer field order would not yield an actual
31/// stable C layout. `size_of::<Handle<T>>()` (16 bytes on a 64-bit host, 12 on
32/// 32-bit) and the `Option<Handle<T>>` niche optimization are *current,
33/// observed* properties of this implementation, verified by
34/// `tests/handle_static_asserts.rs` — a tripwire against silent drift (e.g. a
35/// future `slotmap` minor bump changing `DefaultKey`'s size), not a stable
36/// public contract. If a genuine FFI need arises, the crate would add an
37/// explicit `to_raw`/`from_raw` conversion pair rather than promise this
38/// struct's layout.
39///
40/// [`Region`]: crate::Region
41pub struct Handle<T> {
42 /// Declared before `key` so the struct's own field order matches
43 /// `Ord`'s comparison order below — if a future refactor ever swaps the
44 /// hand-written `Ord`/`PartialOrd` impls for `#[derive(PartialOrd, Ord)]`
45 /// (which compares fields in declaration order), this keeps that
46 /// substitution field-order-neutral instead of silently reordering
47 /// comparisons. Layout-neutral either way: `size_of::<Handle<T>>()` and
48 /// its alignment are unaffected by this field's position (verified by
49 /// `tests/handle_static_asserts.rs`, which pins the size on both
50 /// pointer widths).
51 pub(crate) region_id: NonZeroUsize,
52 /// Crate-visible so [`Region`](crate::Region) can build and read a handle,
53 /// never exposed publicly.
54 pub(crate) key: slotmap::DefaultKey,
55 _ty: PhantomData<fn() -> T>,
56}
57
58impl<T> Handle<T> {
59 /// Crate-internal constructor wrapping a raw slotmap key and region ID.
60 pub(crate) fn from_key_and_region(region_id: NonZeroUsize, key: slotmap::DefaultKey) -> Self {
61 Self {
62 region_id,
63 key,
64 _ty: PhantomData,
65 }
66 }
67}
68
69// Hand-written impls: a handle's identity is the pair `(region_id, key)`, so these
70// impls must hold for *every* `T`, not only `T: Clone`/`Eq`/… that `#[derive]` would
71// (wrongly) require. They delegate to the inner fields and hold unconditionally in `T`.
72impl<T> Clone for Handle<T> {
73 fn clone(&self) -> Self {
74 *self
75 }
76}
77impl<T> Copy for Handle<T> {}
78impl<T> PartialEq for Handle<T> {
79 fn eq(&self, other: &Self) -> bool {
80 self.key == other.key && self.region_id == other.region_id
81 }
82}
83impl<T> Eq for Handle<T> {}
84impl<T> core::hash::Hash for Handle<T> {
85 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
86 self.key.hash(state);
87 self.region_id.hash(state);
88 }
89}
90
91// Comparison order: first by `region_id`, then by `key`.
92//
93// This is a deliberate design choice (not a `Hash`-impl-parity default —
94// field order in `Hash` has no bearing on `Ord`/`Eq` consistency, and any
95// order here is equally valid for that purpose): ordering by `region_id`
96// first means handles group by their owning `Region` under `sort()` /
97// inside a `BTreeMap<Handle<T>, V>` / `BTreeSet<Handle<T>>`, so a caller
98// holding handles from several `Region`s can range-scan just one Region's
99// slice contiguously (e.g. `handles.sort()` then `.partition_point(..)` /
100// a `BTreeMap` range bounded by that Region's own handles). Ordering by
101// `key` first (the alternative) would instead interleave handles from
102// different Regions whenever their raw `DefaultKey`s happen to compare
103// close together — which is common, since the first insert into any fresh
104// `Region` tends to produce the same key — defeating exactly the grouping
105// a `BTreeMap`/sorted-`Vec` user would reasonably want.
106//
107// Handles from different regions (different `region_id`) will never
108// compare equal per `PartialEq`, but they still have a consistent total
109// order — useful for sorting/`BTreeMap` even though `HashMap` is the more
110// common use case.
111/// `Ord`/`PartialOrd` provide a total order consistent with [`Eq`], suitable
112/// for storing `Handle<T>` in a `BTreeMap`/`BTreeSet` or sorting a `Vec` of
113/// them. The *relative* order between two particular handles — including
114/// whether handles from different [`Region`](crate::Region)s group together
115/// or interleave — is an unspecified implementation detail (currently:
116/// group by `region_id`, tie-break by `key`) and may change in any release.
117/// Do not depend on it for anything beyond "a total order exists".
118impl<T> PartialOrd for Handle<T> {
119 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
120 Some(self.cmp(other))
121 }
122}
123
124impl<T> Ord for Handle<T> {
125 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
126 match self.region_id.cmp(&other.region_id) {
127 core::cmp::Ordering::Equal => self.key.cmp(&other.key),
128 ordering => ordering,
129 }
130 }
131}
132
133impl<T> core::fmt::Debug for Handle<T> {
134 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
135 f.debug_struct("Handle")
136 .field("region_id", &self.region_id)
137 .field("key", &self.key)
138 .finish()
139 }
140}