fj_core/storage/handle.rs
1use std::{
2 any::type_name, borrow::Borrow, cmp::Ordering, fmt, hash::Hash, ops::Deref,
3};
4
5use super::{blocks::Index, store::StoreInner};
6
7/// # A handle that references a stored object
8///
9/// You can get an instance of `Handle` by inserting an object into a store. A
10/// handle dereferences to the object it points to, via its [`Deref`]
11/// implementation.
12///
13/// ## Bare objects and stored objects
14///
15/// A bare object is just that: an instance of a bare object type. Once a bare
16/// objects is inserted into storage, it becomes a stored object. A stored
17/// object is owned by the store, and can be referenced through instances of
18/// `Handle`.
19///
20/// The point of doing this, is to provide objects with a unique identity, via
21/// their location within storage. The importance of this is expanded upon in
22/// the next section.
23///
24/// ## Equality and Identity
25///
26/// Most objects have [`Eq`]/[`PartialEq`] implementations that can be used to
27/// determine equality. Those implementations are derived, meaning two objects
28/// are equal, if all of their fields are equal. This can be used to compare
29/// objects structurally. [`Handle`]'s own [`Eq`]/[`PartialEq`] implementations
30/// defer to those of the stored object it references.
31///
32/// However, that two objects are *equal* does not mean they are *identical*.
33///
34/// This distinction is relevant, because non-identical objects that are
35/// *supposed* to be equal can in fact end up equal, if they are created based
36/// on simple input data (as you might have in a unit test). But they might end
37/// up slightly different, if they are created based on complex input data (as
38/// you might have in a real-world scenario). This situation would most likely
39/// result in a bug that is not easily caught in testing.
40///
41/// You can compare the identity of two `Handle`s, by comparing the values
42/// returned by [`Handle::id`].
43///
44/// ### Validation Must Use Identity
45///
46/// To prevent situations where everything looks fine during development, but
47/// you end up with a bug in production, any validation code that compares
48/// objects and expects them to be the same, must do that comparison based on
49/// identity, not equality. That way, this problem can never happen, because we
50/// never expect non-identical objects to be equal.
51pub struct Handle<T> {
52 pub(super) store: StoreInner<T>,
53 pub(super) index: Index,
54 pub(super) ptr: *const Option<T>,
55}
56
57impl<T> Handle<T> {
58 /// Access the object's unique id
59 pub fn id(&self) -> ObjectId {
60 ObjectId::from_ptr(self.ptr)
61 }
62
63 /// Return a bare object, which is a clone of the referenced stored object
64 pub fn clone_object(&self) -> T
65 where
66 T: Clone,
67 {
68 self.deref().clone()
69 }
70}
71
72impl<T> Deref for Handle<T> {
73 type Target = T;
74
75 fn deref(&self) -> &Self::Target {
76 // `Handle` keeps a reference to `StoreInner`. Since that is an `Arc`
77 // under the hood, we know that as long as an instance of `Handle`
78 // exists, the `StoreInner` its data lives in is still alive. Even if
79 // the `Store` was dropped.
80 //
81 // The `Store` API ensures two things:
82 //
83 // 1. That no `Handle` is ever created, until the object it references
84 // has at least been reserved.
85 // 2. That the memory objects live in is never deallocated.
86 //
87 // That means that as long as a `Handle` exists, the object it
88 // references has at least been reserved, and has not been deallocated.
89 //
90 // Given all this, we know that the following must be true:
91 //
92 // - The pointer is not null.
93 // - The pointer is properly aligned.
94 // - The pointer is dereferenceable.
95 // - The pointer points to an initialized instance of `T`.
96 //
97 // Further, there is no way to (safely) get a `&mut` reference to any
98 // object in a `Store`/`Block`. So we know that the aliasing rules for
99 // the reference we return here are enforced.
100 //
101 // Furthermore, all of the code mentioned here is covered by unit tests,
102 // which I've run successfully under Miri.
103 let slot = unsafe { &*self.ptr };
104
105 // Can only panic, if the object was reserved, but the reservation has
106 // never been completed.
107 slot.as_ref()
108 .expect("Handle references non-existing object")
109 }
110}
111
112impl<T> Borrow<T> for Handle<T> {
113 fn borrow(&self) -> &T {
114 self.deref()
115 }
116}
117
118impl<T> Clone for Handle<T> {
119 fn clone(&self) -> Self {
120 Self {
121 store: self.store.clone(),
122 index: self.index,
123 ptr: self.ptr,
124 }
125 }
126}
127
128impl<T> Eq for Handle<T> where T: Eq {}
129
130impl<T> PartialEq for Handle<T>
131where
132 T: PartialEq,
133{
134 fn eq(&self, other: &Self) -> bool {
135 self.deref().eq(other.deref())
136 }
137}
138
139impl<T> Hash for Handle<T>
140where
141 T: Hash,
142{
143 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
144 self.deref().hash(state);
145 }
146}
147
148impl<T> Ord for Handle<T>
149where
150 T: Ord,
151{
152 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
153 self.deref().cmp(other.deref())
154 }
155}
156
157impl<T> PartialOrd for Handle<T>
158where
159 T: PartialOrd,
160{
161 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
162 self.deref().partial_cmp(other.deref())
163 }
164}
165
166impl<T> fmt::Debug for Handle<T>
167where
168 T: fmt::Debug,
169{
170 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
171 let name = {
172 let type_name = type_name::<T>();
173 match type_name.rsplit_once("::") {
174 Some((_, name)) => name,
175 None => type_name,
176 }
177 };
178 let id = self.id().0;
179 let object = self.deref();
180
181 if f.alternate() {
182 write!(f, "{name} @ {id:#x} => {object:#?}")?;
183 } else {
184 write!(f, "{name} @ {id:#x}")?;
185 }
186
187 Ok(())
188 }
189}
190
191impl<T> From<HandleWrapper<T>> for Handle<T> {
192 fn from(wrapper: HandleWrapper<T>) -> Self {
193 wrapper.0
194 }
195}
196
197unsafe impl<T> Send for Handle<T> {}
198unsafe impl<T> Sync for Handle<T> {}
199
200/// The unique ID of a stored object
201///
202/// You can access a stored object's ID via [`Handle::id`]. Please refer to the
203/// documentation of [`Handle`] for an explanation of object identity.
204#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd)]
205pub struct ObjectId(pub(crate) u64);
206
207impl ObjectId {
208 pub(crate) fn from_ptr<T>(ptr: *const T) -> ObjectId {
209 Self(ptr as u64)
210 }
211}
212
213impl fmt::Debug for ObjectId {
214 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
215 let id = self.0;
216 write!(f, "object id {id:#x}")
217 }
218}
219
220/// A wrapper around [`Handle`] that defines equality based on identity
221///
222/// `HandleWrapper` implements [`Eq`]/[`PartialEq`] and other common traits
223/// that are based on those, based on the identity of a stored object that the
224/// wrapped [`Handle`] references.
225///
226/// This is useful, since some objects are empty (meaning, they don't contain
227/// any data, and don't reference other objects). Such objects only exist to be
228/// distinguished based on their identity. But since a bare object doesn't have
229/// an identity yet, there's no meaningful way to implement [`Eq`]/[`PartialEq`]
230/// for such a bare object type.
231///
232/// However, such objects are referenced by other objects, and if we want to
233/// derive [`Eq`]/[`PartialEq`] for a referencing object, we need something that
234/// can provide [`Eq`]/[`PartialEq`] implementations for the empty objects. That
235/// is the purpose of `HandleWrapper`.
236pub struct HandleWrapper<T>(pub Handle<T>);
237
238impl<T> HandleWrapper<T> {
239 /// Convert `&self` into a `&Handle`
240 pub fn as_handle(&self) -> &Handle<T> {
241 &self.0
242 }
243
244 /// Convert `self` into a `Handle`
245 pub fn into_handle(self) -> Handle<T> {
246 self.0
247 }
248}
249
250impl<T> Deref for HandleWrapper<T> {
251 type Target = Handle<T>;
252
253 fn deref(&self) -> &Self::Target {
254 &self.0
255 }
256}
257
258impl<T> Clone for HandleWrapper<T> {
259 fn clone(&self) -> Self {
260 Self(self.0.clone())
261 }
262}
263
264impl<T> Eq for HandleWrapper<T> {}
265
266impl<T> PartialEq for HandleWrapper<T> {
267 fn eq(&self, other: &Self) -> bool {
268 self.0.id().eq(&other.0.id())
269 }
270}
271
272impl<T> Hash for HandleWrapper<T> {
273 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
274 self.0.id().hash(state);
275 }
276}
277
278impl<T> Ord for HandleWrapper<T> {
279 fn cmp(&self, other: &Self) -> Ordering {
280 self.0.id().cmp(&other.0.id())
281 }
282}
283
284impl<T> PartialOrd for HandleWrapper<T> {
285 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
286 Some(self.cmp(other))
287 }
288}
289
290impl<T> fmt::Debug for HandleWrapper<T>
291where
292 T: fmt::Debug,
293{
294 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
295 self.0.fmt(f)
296 }
297}
298
299impl<T> From<Handle<T>> for HandleWrapper<T> {
300 fn from(handle: Handle<T>) -> Self {
301 Self(handle)
302 }
303}
304
305unsafe impl<T> Send for HandleWrapper<T> {}
306unsafe impl<T> Sync for HandleWrapper<T> {}