slicec/utils/
ptr_util.rs

1// Copyright (c) ZeroC, Inc.
2
3use std::any::TypeId;
4
5/// Represents a pointer that owns the data it's pointing to.
6/// When this pointer is dropped, it ensures the pointed to data is dropped as well.
7///
8/// This can be used in conjunction with [`WeakPtr`] to form complex (and sometimes cyclic) data structures while still
9/// adhering to Rust's ownership rules, and avoiding un-droppable memory cycles.
10#[derive(Debug)]
11pub struct OwnedPtr<T: ?Sized> {
12    data: Box<T>,
13    concrete_type_id: TypeId,
14}
15
16impl<T: Sized + 'static> OwnedPtr<T> {
17    pub fn new(value: T) -> Self {
18        OwnedPtr {
19            data: Box::new(value),
20            concrete_type_id: TypeId::of::<T>(),
21        }
22    }
23}
24
25impl<T: ?Sized> OwnedPtr<T> {
26    #[allow(clippy::should_implement_trait)]
27    pub fn borrow(&self) -> &T {
28        &self.data
29    }
30
31    /// # Safety
32    ///
33    /// This function doesn't use unsafe Rust, but is marked unsafe because the invoker must
34    /// GUARANTEE there are no other references to the underlying data when calling this function.
35    ///
36    /// The borrow checker can ensure that there are no other references through this `OwnedPtr`,
37    /// but it's possible to obtain a reference via `WeakPtr::borrow` instead. Because `WeakPtr`
38    /// uses raw pointers, the borrow checker can't reason about these accesses. So we have to
39    /// enforce Rust's borrow policy manually by ensuring this mutable borrow is the ONLY borrow.
40    ///
41    /// Mutating the underlying data while another reference to it still exists, is undefined
42    /// behavior. So ONLY call this function if you are CERTAIN that NO other references exist.
43    #[allow(clippy::should_implement_trait)]
44    pub unsafe fn borrow_mut(&mut self) -> &mut T {
45        &mut self.data
46    }
47
48    pub fn downgrade(&self) -> WeakPtr<T> {
49        WeakPtr {
50            data: Some(&*self.data),
51            concrete_type_id: self.concrete_type_id,
52        }
53    }
54
55    pub fn downcast<U: 'static>(self) -> Result<OwnedPtr<U>, OwnedPtr<T>> {
56        // Make sure that the original concrete type of this `OwnedPtr` matches the requested type.
57        // If it doesn't, return an error holding the uncasted `OwnedPtr`.
58        if self.concrete_type_id == TypeId::of::<U>() {
59            // Convert the underlying box into a raw pointer so we can forcibly cast it.
60            let inner = Box::into_raw(self.data);
61            // Cast the pointer to the original concrete type and re-box it.
62            let converted = unsafe { Box::from_raw(inner as *mut U) };
63            // Construct a new OwnedPtr with the downcasted type.
64            Ok(OwnedPtr::from_inner((converted, self.concrete_type_id)))
65        } else {
66            Err(self)
67        }
68    }
69
70    pub fn from_inner(inner: (Box<T>, TypeId)) -> Self {
71        OwnedPtr {
72            data: inner.0,
73            concrete_type_id: inner.1,
74        }
75    }
76
77    pub fn into_inner(self) -> (Box<T>, TypeId) {
78        (self.data, self.concrete_type_id)
79    }
80}
81
82/// Represents a pointer that only references the data it's pointing to.
83/// Unlike [`OwnedPtr`], dropping this pointer has no effect on the underlying data, this pointer can only immutably
84/// access the underlying data, and care must be taken to prevent this pointer from dangling.
85///
86/// This can be used in conjunction with [`OwnedPtr`] to form complex (and sometimes cyclic) data structures while
87/// still adhering to Rust's ownership rules, and avoiding un-droppable memory cycles.
88#[derive(Debug)]
89pub struct WeakPtr<T: ?Sized> {
90    data: Option<*const T>,
91    concrete_type_id: TypeId,
92}
93
94impl<T: ?Sized + 'static> WeakPtr<T> {
95    pub fn create_uninitialized() -> Self {
96        WeakPtr {
97            data: None,
98            concrete_type_id: TypeId::of::<T>(),
99        }
100    }
101}
102
103impl<T: ?Sized> WeakPtr<T> {
104    pub fn is_initialized(&self) -> bool {
105        self.data.is_some()
106    }
107
108    // This isn't marked as unsafe because it's assumed all WeakPtr live inside the AST, alongside
109    // the OwnedPtr. Since the entire AST goes out of scope at the same time when the program ends,
110    // it's impossible to have a dangling pointer here, and so, this function is always safe.
111    //
112    // Note that it IS still possible to call this on an uninitialized WeakPtr, which will cause a
113    // panic. But this isn't 'unsafe' in the technical sense of involving unsafe Rust.
114    #[allow(clippy::should_implement_trait)]
115    pub fn borrow(&self) -> &T {
116        unsafe { &*self.data.unwrap() }
117    }
118
119    pub fn downcast<U: 'static>(self) -> Result<WeakPtr<U>, WeakPtr<T>> {
120        // Make sure that the original concrete type of this `WeakPtr` matches the requested type.
121        // If it doesn't, return an error holding the uncasted `WeakPtr`.
122        if self.concrete_type_id == TypeId::of::<U>() {
123            // Forcibly downcast the underlying pointer to the original concrete type.
124            let converted = self.data.map(|ptr| ptr as *const U);
125            // Construct a new WeakPtr with the downcasted type.
126            Ok(WeakPtr::from_inner((converted, self.concrete_type_id)))
127        } else {
128            Err(self)
129        }
130    }
131
132    pub fn from_inner(inner: (Option<*const T>, TypeId)) -> Self {
133        WeakPtr {
134            data: inner.0,
135            concrete_type_id: inner.1,
136        }
137    }
138
139    pub fn into_inner(self) -> (Option<*const T>, TypeId) {
140        (self.data, self.concrete_type_id)
141    }
142}
143
144impl<T: ?Sized> Clone for WeakPtr<T> {
145    fn clone(&self) -> Self {
146        WeakPtr {
147            data: self.data,
148            concrete_type_id: self.concrete_type_id,
149        }
150    }
151}
152
153// It is safe to send and share these pointers between threads, since they do not use interior mutability.
154// It is impossible to mutate the pointed-to data through a `WeakPtr`, and mutating the pointed-to data through an
155// `OwnedPtr` is only possible through a mutable reference to the pointer itself, guaranteeing exclusivity.
156// Additionally, both `WeakPtr` and `OwnedPtr` are covariant over `T`, and the lifetimes of references through them.
157unsafe impl<T: ?Sized + Send> Send for OwnedPtr<T> {}
158unsafe impl<T: ?Sized + Sync> Sync for OwnedPtr<T> {}
159unsafe impl<T: ?Sized + Send> Send for WeakPtr<T> {}
160unsafe impl<T: ?Sized + Sync> Sync for WeakPtr<T> {}
161
162// TODO
163// Implementing these traits would give our pointers support for implicit upcasting (casting a
164// concrete type to a trait type it implements). But the trait is still marked as unstable.
165// When it's stabilized, this should be uncommented, and the macros beneath this deleted.
166//
167// impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<OwnedPtr<U>> for OwnedPtr<T> {}
168// impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<WeakPtr<U>> for WeakPtr<T> {}
169
170#[macro_export]
171macro_rules! downgrade_as {
172    ($owned:expr, $new_type:ty) => {
173        $crate::upcast_weak_as!($owned.downgrade(), $new_type)
174    };
175}
176
177#[macro_export]
178macro_rules! upcast_owned_as {
179    ($owned:expr, $new_type:ty) => {{
180        let (data, type_id) = $owned.into_inner();
181        OwnedPtr::from_inner((data as Box<$new_type>, type_id))
182    }};
183}
184
185#[macro_export]
186macro_rules! upcast_weak_as {
187    ($weak:expr, $new_type:ty) => {{
188        let (data, type_id) = $weak.into_inner();
189        WeakPtr::from_inner((data.map(|ptr| ptr as *const $new_type), type_id))
190    }};
191}
192
193impl<'a, T: ?Sized, U: ?Sized> PartialEq<&'a T> for OwnedPtr<U> {
194    /// Returns true if this pointer and the provided reference both point to the same memory address.
195    ///
196    /// Note that this may return true in some unintuitive/exotic cases:
197    /// - If you have 2 references to the same piece of data, with different types, this will return true. For example,
198    ///   comparing `String` to `dyn Display` is valid, and will return true if they're actually the same.
199    /// - If one or both of the types are zero-sized: since it's address may overlap with another piece of data.
200    /// - Comparing the address of a struct to the address of its first field: these are conceptually different things,
201    ///   but both live at the same address, since structs are stored as a list of it's fields.
202    ///
203    /// See <https://doc.rust-lang.org/std/ptr/fn.eq.html> for more information. This function uses the same semantics.
204    fn eq(&self, other: &&'a T) -> bool {
205        // Convert this pointer's box and the other borrow to raw pointers, then strip their typing, and convert any
206        // DST fat pointers to thin pointers to avoid checking their v-tables (which can be transient).
207        let self_ptr = (&*self.data as *const U).cast::<()>();
208        let other_ptr = (*other as *const T).cast::<()>();
209        // Check if the data pointers point to the same location in memory.
210        std::ptr::eq(self_ptr, other_ptr)
211    }
212}
213
214impl<'a, T: ?Sized, U: ?Sized> PartialEq<&'a T> for WeakPtr<U> {
215    /// Returns true if this pointer and the provided reference both point to the same memory address.
216    ///
217    /// Note that this may return true in some unintuitive/exotic cases:
218    /// - If you have 2 references to the same piece of data, with different types, this will return true. For example,
219    ///   comparing `String` to `dyn Display` is valid, and will return true if they're actually the same.
220    /// - If one or both of the types are zero-sized: since it's address may overlap with another piece of data.
221    /// - Comparing the address of a struct to the address of its first field: these are conceptually different things,
222    ///   but both live at the same address, since structs are stored as a list of it's fields.
223    ///
224    /// See <https://doc.rust-lang.org/std/ptr/fn.eq.html> for more information. This function uses the same semantics.
225    fn eq(&self, other: &&'a T) -> bool {
226        // Convert the other borrow to a raw pointer, then strip it and this pointer's typing, and convert any
227        // DST fat pointers to thin pointers to avoid checking their v-tables (which can be transient).
228        let Some(self_ptr) = self.data else { return false };
229        let other_ptr = (*other as *const T).cast::<()>();
230        // Check if the data pointers point to the same location in memory.
231        std::ptr::eq(self_ptr.cast::<()>(), other_ptr)
232    }
233}
234
235#[cfg(test)]
236mod tests {
237    use super::*;
238
239    // Ensure that it's valid to have multiple immutable borrows to a piece a data through
240    // the pointer that owns it, and any weak pointers created from it.
241    #[test]
242    fn multiple_immutable_borrows_is_legal() {
243        // Arrange
244        let owned_ptr = OwnedPtr::new(79_u32);
245        let weak_ptr1 = owned_ptr.downgrade();
246        let weak_ptr2 = owned_ptr.downgrade();
247
248        let borrow0 = owned_ptr.borrow();
249        let borrow1 = weak_ptr1.borrow();
250        let borrow2 = weak_ptr2.borrow();
251
252        // Act
253        // Use all the borrowed values to prevent the compiler from dropping them pre-maturely.
254        let dummy = borrow0 + borrow1 + borrow2;
255
256        // Assert
257        assert_eq!(dummy, 3 * 79);
258    }
259
260    // Ensure that accessing an uninitialized pointer causes a panic.
261    // It's impossible to construct an uninitialized `OwnedPtr`, so we only check `WeakPtr`.
262    #[test]
263    #[should_panic]
264    fn accessing_uninitialized_pointer_causes_panic() {
265        // Arrange
266        let weak_ptr: WeakPtr<String> = WeakPtr::create_uninitialized();
267
268        // Act
269        weak_ptr.borrow();
270
271        // Assert
272        // This function should panic in the 'act' section. This is 'asserted' by the 'should_panic' attribute on it.
273    }
274
275    #[test]
276    fn pointer_equality_is_reflexive() {
277        // Arrange
278        let owned_ptr = OwnedPtr::new("test".to_owned());
279        let weak_ptr = owned_ptr.downgrade();
280
281        // Act/Assert: asserting that they're equal is the 'act'.
282        assert_eq!(owned_ptr, owned_ptr.borrow());
283        assert_eq!(owned_ptr, weak_ptr.borrow());
284        assert_eq!(weak_ptr, owned_ptr.borrow());
285        assert_eq!(weak_ptr, weak_ptr.borrow());
286    }
287
288    // Ensure that two pointers that point to the same memory location are equal, even if they hold different types.
289    #[test]
290    fn pointer_equality_is_type_independent() {
291        // Arrange
292        let owned_ptr: OwnedPtr<String> = OwnedPtr::new("test".to_owned());
293
294        // Create a weak pointer to the string.
295        let weak_ptr: WeakPtr<String> = owned_ptr.downgrade();
296        // Rip it apart and manually cast the pointer from `String` to `bool`.
297        let (raw_pointer, type_id) = weak_ptr.into_inner();
298        let casted_pointer = raw_pointer.map(|ptr| ptr as *const bool);
299        // Re-assemble the weak pointer with the casted type.
300        // This is safe and legal to do in Rust, but borrowing from this pointer in any way would be unsafe.
301        let casted_weak_ptr: WeakPtr<bool> = WeakPtr::from_inner((casted_pointer, type_id));
302
303        // Act/Assert: asserting that they're equal is the 'act'.
304        assert_eq!(casted_weak_ptr, owned_ptr.borrow());
305    }
306
307    // Ensure that two pointers that point to different memory locations are unequal, even if they point to equal data.
308    #[test]
309    fn different_pointers_are_not_equal() {
310        // Arrange
311        let owned_ptr1 = OwnedPtr::new(79_i32);
312        let weak_ptr1 = owned_ptr1.downgrade();
313
314        let owned_ptr2 = OwnedPtr::new(79_i32);
315        let weak_ptr2 = owned_ptr2.downgrade();
316
317        // Act/Assert: asserting that they're not equal is the 'act'.
318        assert_ne!(owned_ptr1, owned_ptr2.borrow());
319        assert_ne!(owned_ptr1, weak_ptr2.borrow());
320
321        assert_ne!(weak_ptr1, owned_ptr2.borrow());
322        assert_ne!(weak_ptr1, weak_ptr2.borrow());
323
324        assert_ne!(owned_ptr2, owned_ptr1.borrow());
325        assert_ne!(owned_ptr2, weak_ptr1.borrow());
326
327        assert_ne!(weak_ptr2, owned_ptr1.borrow());
328        assert_ne!(weak_ptr2, weak_ptr1.borrow());
329    }
330}