Skip to main content

fbl/
ref_ptr.rs

1// Copyright 2026 The Fuchsia Authors
2//
3// Use of this source code is governed by a MIT-style
4// license that can be found in the LICENSE file or at
5// https://opensource.org/licenses/MIT
6
7use crate::recyclable::{Recyclable, UninitRecyclable};
8use crate::ref_counted::HasRefCount;
9use core::mem::MaybeUninit;
10use core::ops::Deref;
11use core::ptr::NonNull;
12use kalloc::AllocError;
13
14use pin_init::{Init, PinInit};
15
16/// `RefPtr<T>` holds a reference to an intrusively-refcounted object of type
17/// T that deletes the object when the refcount drops to 0.
18///
19/// T should be a struct that contains a `fbl::RefCounted` field and implements
20/// `HasRefCount` and `Destroy` traits.
21#[repr(C)]
22pub struct RefPtr<T>
23where
24    T: HasRefCount + Recyclable,
25{
26    ptr: NonNull<T>,
27}
28
29impl<T: HasRefCount + Recyclable> RefPtr<T> {
30    /// Constructs a `RefPtr` from a raw pointer that has already been adopted.
31    ///
32    /// # Safety
33    ///
34    /// - The caller must ensure that `ptr` is valid and has a ref count already
35    ///   acquired.
36    /// - `ptr` must have been allocated in such a way that calling `T::recycle(ptr)` is a
37    ///   correct way to deallocate the pointer.
38    pub unsafe fn from_raw(ptr: *const T) -> Self {
39        // SAFETY: The caller must ensure that ptr is valid.
40        unsafe { RefPtr { ptr: NonNull::new_unchecked(ptr as *mut T) } }
41    }
42
43    /// Constructs a `RefPtr` from a raw pointer that has already been adopted, unless the pointer
44    /// is null.
45    ///
46    /// # Safety
47    ///
48    /// The caller must ensure that `ptr` is either null or, if not null that:
49    /// - a ref count already acquired.
50    /// - has been allocated in such a way that calling `T::recycle(ptr)` is a correct way to
51    ///   deallocate the pointer.
52    pub unsafe fn try_from_raw(ptr: *const T) -> Option<Self> {
53        NonNull::new(ptr as *mut T).map(|ptr| RefPtr { ptr })
54    }
55
56    /// Helper function that allocates a new instance of `T` using `T::allocate` and
57    /// returns a `RefPtr` wrapping it.
58    ///
59    /// This is an internal helper function that should not be used directly.
60    /// Use the `make_ref_counted!(...)` macro instead of this function to properly
61    /// initialize the ref count.
62    ///
63    /// # Safety
64    ///
65    /// The caller must ensure that `T` has a RefCounted field that is not
66    /// already adopted.
67    pub unsafe fn try_new(value: T) -> Result<RefPtr<T>, AllocError> {
68        let mut ptr = T::allocate(value)?;
69        // SAFETY: The caller must ensure that T has a RefCounted field that is not
70        // already adopted.
71        unsafe { ptr.as_mut().ref_count().adopt() };
72        Ok(RefPtr { ptr })
73    }
74
75    /// Returns the raw pointer to the object.
76    pub fn as_ptr(this: &Self) -> *const T {
77        this.ptr.as_ptr()
78    }
79
80    /// Returns `true` if the two `RefPtr`s point to the same object.
81    pub fn ptr_eq(a: &Self, b: &Self) -> bool {
82        a.ptr == b.ptr
83    }
84
85    /// Consume the `RefPtr` and return the raw pointer without modifying the ref count.
86    ///
87    /// The caller is responsible for maintaining the reference count.
88    pub fn into_raw(this: Self) -> *const T {
89        let ptr = this.ptr;
90        core::mem::forget(this);
91        ptr.as_ptr()
92    }
93
94    /// Use the given pin-initializer to pin-initialize a `T` inside of a new `RefPtr`.
95    pub fn try_pin_init<E>(init: impl PinInit<T, E>) -> Result<Self, E>
96    where
97        T: UninitRecyclable,
98        E: From<AllocError>,
99    {
100        let ptr = T::allocate_uninit()?;
101        let guard = UninitRefGuard { ptr };
102        let slot = guard.ptr.as_ptr() as *mut T;
103        // SAFETY: `slot` is valid and will not be moved.
104        unsafe { init.__pinned_init(slot)? };
105        // SAFETY: The object is now initialized, so we can access its ref_count.
106        unsafe { (*slot).ref_count().adopt() };
107        let initialized_ptr = guard.ptr.cast::<T>();
108        core::mem::forget(guard);
109        let initialized_ref = RefPtr { ptr: initialized_ptr };
110        Ok(initialized_ref)
111    }
112
113    /// Use the given initializer to in-place initialize a `T` inside of a new `RefPtr`.
114    pub fn try_init<E>(init: impl Init<T, E>) -> Result<Self, E>
115    where
116        T: UninitRecyclable,
117        E: From<AllocError>,
118    {
119        let ptr = T::allocate_uninit()?;
120        let guard = UninitRefGuard { ptr };
121        let slot = guard.ptr.as_ptr() as *mut T;
122        // SAFETY: `slot` is valid.
123        unsafe { init.__init(slot)? };
124        // SAFETY: The object is now initialized, so we can access its ref_count.
125        unsafe { (*slot).ref_count().adopt() };
126        let initialized_ptr = guard.ptr.cast::<T>();
127        core::mem::forget(guard);
128        Ok(RefPtr { ptr: initialized_ptr })
129    }
130
131    /// Use the given pin-initializer to pin-initialize a `T` inside of a new `RefPtr`.
132    #[inline]
133    pub fn pin_init(init: impl PinInit<T, core::convert::Infallible>) -> Result<Self, AllocError>
134    where
135        T: UninitRecyclable,
136    {
137        let init = unsafe {
138            ::pin_init::pin_init_from_closure(|slot| {
139                init.__pinned_init(slot).map_err(|i| match i {})
140            })
141        };
142        Self::try_pin_init(init)
143    }
144
145    /// Use the given initializer to in-place initialize a `T` inside of a new `RefPtr`.
146    #[inline]
147    pub fn init(init: impl Init<T, core::convert::Infallible>) -> Result<Self, AllocError>
148    where
149        T: UninitRecyclable,
150    {
151        let init = unsafe {
152            ::pin_init::init_from_closure(|slot| init.__init(slot).map_err(|i| match i {}))
153        };
154        Self::try_init(init)
155    }
156}
157
158impl<T: HasRefCount + Recyclable> Deref for RefPtr<T> {
159    type Target = T;
160    fn deref(&self) -> &Self::Target {
161        unsafe { self.ptr.as_ref() }
162    }
163}
164
165impl<T: HasRefCount + Recyclable> Clone for RefPtr<T> {
166    fn clone(&self) -> Self {
167        self.deref().ref_count().add_ref();
168        RefPtr { ptr: self.ptr }
169    }
170}
171
172impl<T: HasRefCount + Recyclable> Drop for RefPtr<T> {
173    fn drop(&mut self) {
174        if self.deref().ref_count().release() {
175            unsafe {
176                T::recycle(self.ptr);
177            }
178        }
179    }
180}
181
182impl<T: HasRefCount + Recyclable> PartialEq for RefPtr<T> {
183    fn eq(&self, other: &Self) -> bool {
184        RefPtr::ptr_eq(self, other)
185    }
186}
187
188impl<T: HasRefCount + Recyclable> Eq for RefPtr<T> {}
189
190unsafe impl<T: HasRefCount + Recyclable + Send + Sync> Send for RefPtr<T> {}
191unsafe impl<T: HasRefCount + Recyclable + Send + Sync> Sync for RefPtr<T> {}
192
193struct UninitRefGuard<T: UninitRecyclable> {
194    ptr: NonNull<MaybeUninit<T>>,
195}
196
197impl<T: UninitRecyclable> Drop for UninitRefGuard<T> {
198    fn drop(&mut self) {
199        unsafe {
200            T::recycle_uninit(self.ptr);
201        }
202    }
203}
204
205/// Macro to construct a RefPtr, automatically populating the ref_count field.
206#[macro_export]
207macro_rules! make_ref_counted {
208    ($ty:ident { $($field:ident : $val:expr),* $(,)? }) => {
209        // SAFETY: The macro creates a new object with a ref count of 1.
210        unsafe {
211            $crate::RefPtr::try_new($ty {
212                ref_count: $crate::RefCounted::new(),
213                __fbl_ref_counted_guard: (),
214                $($field : $val),*
215            })
216        }
217    };
218}
219
220/// Macro to construct a RefPtr with pin-initialization, automatically populating the ref_count field.
221#[macro_export]
222macro_rules! pin_make_ref_counted {
223    ($ty:ident { $($field:tt)* }) => {
224        $crate::RefPtr::pin_init($crate::pin_init::pin_init!($ty {
225            ref_count: $crate::RefCounted::new(),
226            __fbl_ref_counted_guard: (),
227            $($field)*
228        }))
229    };
230}
231
232/// Macro to construct a RefPtr with fallible pin-initialization, automatically populating the ref_count field.
233#[macro_export]
234macro_rules! try_pin_make_ref_counted {
235    ($ty:ident { $($field:tt)* }) => {
236        $crate::RefPtr::try_pin_init($crate::pin_init::pin_init!($ty {
237            ref_count: $crate::RefCounted::new(),
238            __fbl_ref_counted_guard: (),
239            $($field)*
240        }))
241    };
242}
243
244#[cfg(test)]
245mod tests {
246    use super::*;
247    use core::ffi::c_void;
248    use core::pin::Pin;
249    use core::ptr::null;
250    use core::sync::atomic::{AtomicBool, Ordering};
251
252    extern crate alloc;
253    use alloc::sync::Arc;
254
255    #[unsafe(no_mangle)]
256    pub extern "C" fn rust_recycle_test_rust_ref_counted(ptr: *mut c_void) {
257        unsafe { TestRustRefCounted::recycle_ffi(ptr) }
258    }
259
260    unsafe extern "C" {
261        fn test_import_rust_ref_counted(ptr: *mut c_void);
262    }
263
264    #[fbl::ref_counted]
265    #[pin_init::pin_data(PinnedDrop)]
266    #[derive(crate::Recyclable)]
267    #[repr(C)]
268    pub struct TestRustRefCounted {
269        destroyed: Arc<AtomicBool>,
270    }
271
272    ::zr::static_assert!(core::mem::size_of::<RefPtr<TestRustRefCounted>>() == 8);
273    ::zr::static_assert!(core::mem::align_of::<RefPtr<TestRustRefCounted>>() == 8);
274    ::zr::static_assert!(core::mem::size_of::<Option<RefPtr<TestRustRefCounted>>>() == 8);
275    ::zr::static_assert!(core::mem::align_of::<Option<RefPtr<TestRustRefCounted>>>() == 8);
276
277    #[pin_init::pinned_drop]
278    impl pin_init::PinnedDrop for TestRustRefCounted {
279        fn drop(self: Pin<&mut Self>) {
280            self.destroyed.store(true, Ordering::Relaxed);
281        }
282    }
283
284    #[test]
285    fn test_rust_drops_reference() {
286        let destroyed = Arc::new(AtomicBool::new(false));
287        {
288            let ref_ptr =
289                make_ref_counted!(TestRustRefCounted { destroyed: destroyed.clone() }).unwrap();
290            assert!(!destroyed.load(Ordering::Relaxed));
291            let ref_ptr_clone = ref_ptr.clone();
292            drop(ref_ptr_clone);
293            assert!(!destroyed.load(Ordering::Relaxed));
294        } // Drop ref_ptr -> count becomes 0 -> calls destroy -> triggers Drop trait!
295
296        assert!(destroyed.load(Ordering::Relaxed));
297    }
298
299    #[test]
300    #[cfg_attr(miri, ignore = "miri does not support calling foreign functions")]
301    fn test_cpp_drops_reference() {
302        let destroyed = Arc::new(AtomicBool::new(false));
303        let ref_ptr =
304            make_ref_counted!(TestRustRefCounted { destroyed: destroyed.clone() }).unwrap();
305        let raw_ptr = RefPtr::into_raw(ref_ptr);
306
307        unsafe {
308            assert!(!destroyed.load(Ordering::Relaxed));
309            // Pass to C++!
310            test_import_rust_ref_counted(raw_ptr as *const TestRustRefCounted as *mut c_void);
311            // C++ should have acquired reference and released it!
312            // And since count was 1, it should have dropped it!
313            assert!(destroyed.load(Ordering::Relaxed));
314        }
315    }
316
317    #[test]
318    fn test_ref_ptr_compare() {
319        let destroyed1 = Arc::new(AtomicBool::new(false));
320        let destroyed2 = Arc::new(AtomicBool::new(false));
321        let ptr1 = make_ref_counted!(TestRustRefCounted { destroyed: destroyed1.clone() }).unwrap();
322        let ptr2 = make_ref_counted!(TestRustRefCounted { destroyed: destroyed2.clone() }).unwrap();
323        let ptr1_clone = ptr1.clone();
324
325        assert!(ptr1 == ptr1);
326        assert!(ptr1 != ptr2);
327        assert!(ptr1 == ptr1_clone);
328    }
329
330    #[test]
331    fn test_rust_pin_init() {
332        let destroyed = Arc::new(AtomicBool::new(false));
333        let destroyed_clone = destroyed.clone();
334        {
335            let ref_ptr =
336                pin_make_ref_counted!(TestRustRefCounted { destroyed: destroyed_clone }).unwrap();
337            assert!(!destroyed.load(Ordering::Relaxed));
338            let ref_ptr_clone = ref_ptr.clone();
339            drop(ref_ptr_clone);
340            assert!(!destroyed.load(Ordering::Relaxed));
341        } // Drop ref_ptr
342        assert!(destroyed.load(Ordering::Relaxed));
343    }
344
345    #[fbl::ref_counted]
346    #[pin_init::pin_data]
347    #[derive(crate::Recyclable)]
348    #[repr(C)]
349    struct FallibleInit {
350        value: i32,
351    }
352
353    #[test]
354    fn test_rust_try_pin_init_fail() {
355        let init = unsafe {
356            ::pin_init::pin_init_from_closure(
357                |_slot: *mut FallibleInit| -> Result<(), AllocError> { Err(AllocError) },
358            )
359        };
360        let res = RefPtr::try_pin_init(init);
361        assert!(res.is_err());
362    }
363
364    #[test]
365    fn test_null_try_from() {
366        let maybe_ref_ptr = unsafe { RefPtr::try_from_raw(null::<TestRustRefCounted>()) };
367        assert!(maybe_ref_ptr.is_none());
368    }
369}