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> {}