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}