Skip to main content

azul_core/
refany.rs

1//! Type-erased, reference-counted smart pointer with runtime borrow checking.
2//!
3//! # Safety
4//!
5//! This module provides `RefAny`, a type-erased container similar to `Arc<RefCell<dyn Any>>`,
6//! but designed for FFI compatibility and cross-language interoperability.
7//!
8//! ## Memory Safety Guarantees
9//!
10//! 1. **Proper Alignment**: Fixed in commit addressing Miri UB - memory is allocated with correct
11//!    alignment for the stored type using `Layout::from_size_align()`.
12//!
13//! 2. **Atomic Reference Counting**: All reference counts use `AtomicUsize` with `SeqCst` ordering,
14//!    ensuring thread-safe access and preventing use-after-free.
15//!
16//! 3. **Runtime Type Safety**: Type IDs are checked before downcasting, preventing invalid pointer
17//!    casts that would cause undefined behavior.
18//!
19//! 4. **Runtime Borrow Checking**: Shared and mutable borrows are tracked at runtime, enforcing
20//!    Rust's borrowing rules dynamically (similar to `RefCell`).
21//!
22//! ## Thread Safety
23//!
24//! - `RefAny` is `Send`: Can be transferred between threads (data is heap-allocated)
25//! - `RefAny` is `Sync`: Can be shared between threads (atomic operations + `&mut self` for
26//!   borrows)
27//!
28//! The `SeqCst` (Sequentially Consistent) memory ordering provides the strongest guarantees:
29//! all atomic operations appear in a single global order visible to all threads, preventing
30//! race conditions where one thread doesn't see another's reference count updates.
31
32use alloc::boxed::Box;
33use alloc::string::String;
34use core::{
35    alloc::Layout,
36    ffi::c_void,
37    fmt,
38    sync::atomic::{AtomicUsize, Ordering as AtomicOrdering},
39};
40
41use azul_css::AzString;
42
43/// C-compatible destructor function type for RefAny.
44/// Called when the last reference to a RefAny is dropped.
45pub type RefAnyDestructorType = extern "C" fn(*mut c_void);
46
47// NOTE: JSON serialization/deserialization callback types are defined in azul_layout::json
48// The actual types are:
49//   RefAnySerializeFnType = extern "C" fn(RefAny) -> Json
50//   RefAnyDeserializeFnType = extern "C" fn(Json) -> ResultRefAnyString
51// In azul_core, we only store function pointers as usize (0 = not set).
52
53/// Internal reference counting metadata for `RefAny`.
54///
55/// This struct tracks:
56///
57/// - How many `RefAny` clones exist (`num_copies`)
58/// - How many shared borrows are active (`num_refs`)
59/// - How many mutable borrows are active (`num_mutable_refs`)
60/// - Memory layout information for correct deallocation
61/// - Type information for runtime type checking
62///
63/// # Thread Safety
64///
65/// All counters are `AtomicUsize` with `SeqCst` ordering, making them safe to access
66/// from multiple threads simultaneously. The strong ordering ensures no thread can
67/// observe inconsistent states (e.g., both seeing count=1 during final drop).
68#[derive(Debug)]
69#[repr(C)]
70pub struct RefCountInner {
71    /// Type-erased pointer to heap-allocated data.
72    ///
73    /// SAFETY: Must be properly aligned for the stored type (guaranteed by
74    /// `Layout::from_size_align` in `new_c`). Never null for non-ZST types.
75    ///
76    /// This pointer is shared by all RefAny clones, so replace_contents
77    /// updates are visible to all clones.
78    pub _internal_ptr: *const c_void,
79
80    /// Number of `RefAny` instances sharing the same data.
81    /// When this reaches 0, the data is deallocated.
82    pub num_copies: AtomicUsize,
83
84    /// Number of active shared borrows (`Ref<T>`).
85    /// While > 0, mutable borrows are forbidden.
86    pub num_refs: AtomicUsize,
87
88    /// Number of active mutable borrows (`RefMut<T>`).
89    /// While > 0, all other borrows are forbidden.
90    pub num_mutable_refs: AtomicUsize,
91
92    /// Size of the stored type in bytes (from `size_of::<T>()`).
93    pub _internal_len: usize,
94
95    /// Layout size for deallocation (from `Layout::size()`).
96    pub _internal_layout_size: usize,
97
98    /// Required alignment for the stored type (from `align_of::<T>()`).
99    /// CRITICAL: Must match the alignment used during allocation to prevent UB.
100    pub _internal_layout_align: usize,
101
102    /// Runtime type identifier computed from `TypeId::of::<T>()`.
103    /// Used to prevent invalid downcasts.
104    pub type_id: u64,
105
106    /// Human-readable type name (e.g., "MyStruct") for debugging.
107    pub type_name: AzString,
108
109    /// Function pointer to correctly drop the type-erased data.
110    /// SAFETY: Must be called with a pointer to data of the correct type.
111    pub custom_destructor: extern "C" fn(*mut c_void),
112
113    /// Function pointer to serialize RefAny to JSON (0 = not set).
114    /// Cast to RefAnySerializeFnType (defined in azul_layout::json) when called.
115    /// Type: extern "C" fn(RefAny) -> Json
116    pub serialize_fn: usize,
117
118    /// Function pointer to deserialize JSON to new RefAny (0 = not set).
119    /// Cast to RefAnyDeserializeFnType (defined in azul_layout::json) when called.
120    /// Type: extern "C" fn(Json) -> ResultRefAnyString
121    pub deserialize_fn: usize,
122}
123
124/// Wrapper around a heap-allocated `RefCountInner`.
125///
126/// This is the shared metadata that all `RefAny` clones point to.
127/// The `RefCount` is responsible for all memory management:
128///
129/// - `RefCount::clone()` increments `num_copies` in RefCountInner
130/// - `RefCount::drop()` decrements `num_copies` and, if it reaches 0:
131///   1. Frees the RefCountInner
132///   2. Calls the custom destructor on the data
133///   3. Deallocates the data memory
134///
135/// # Why `run_destructor: bool`
136///
137/// This flag tracks whether this `RefCount` instance should decrement
138/// `num_copies` when dropped. Set to `true` for all clones (including
139/// those created by `RefAny::clone()` and `AZ_REFLECT` macros).
140/// Set to `false` after the decrement has been performed to prevent
141/// double-decrement.
142#[derive(Hash, PartialEq, PartialOrd, Ord, Eq)]
143#[repr(C)]
144pub struct RefCount {
145    pub ptr: *const RefCountInner,
146    pub run_destructor: bool,
147}
148
149impl fmt::Debug for RefCount {
150    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
151        self.downcast().fmt(f)
152    }
153}
154
155impl Clone for RefCount {
156    /// Clones the RefCount and increments the reference count.
157    ///
158    /// # Safety
159    ///
160    /// This is safe because:
161    /// - The ptr is valid (created from Box::into_raw)
162    /// - num_copies is atomically incremented with SeqCst ordering
163    /// - This ensures the RefCountInner is not freed while clones exist
164    fn clone(&self) -> Self {
165        // CRITICAL: Must increment num_copies so the RefCountInner is not freed
166        // while this clone exists. The C macros (AZ_REFLECT) use AzRefCount_clone
167        // to create Ref/RefMut guards, and those guards must keep the data alive.
168        if !self.ptr.is_null() {
169            unsafe {
170                (*self.ptr).num_copies.fetch_add(1, AtomicOrdering::SeqCst);
171            }
172        }
173        Self {
174            ptr: self.ptr,
175            run_destructor: true,
176        }
177    }
178}
179
180impl Drop for RefCount {
181    /// Decrements the reference count when a RefCount clone is dropped.
182    ///
183    /// If this was the last reference (num_copies reaches 0), this will also
184    /// free the RefCountInner and call the custom destructor.
185    fn drop(&mut self) {
186        // Only decrement if run_destructor is true (meaning this is a clone)
187        // and the pointer is valid
188        if !self.run_destructor || self.ptr.is_null() {
189            return;
190        }
191        self.run_destructor = false;
192
193        // Atomically decrement and get the PREVIOUS value
194        let current_copies = unsafe {
195            (*self.ptr).num_copies.fetch_sub(1, AtomicOrdering::SeqCst)
196        };
197
198        // If previous value wasn't 1, other references still exist
199        if current_copies != 1 {
200            return;
201        }
202
203        // We're the last reference! Clean up.
204        // SAFETY: ptr came from Box::into_raw, and we're the last reference
205        let sharing_info = unsafe { Box::from_raw(self.ptr as *mut RefCountInner) };
206        let sharing_info = *sharing_info; // Box deallocates RefCountInner here
207
208        // Get the data pointer
209        let data_ptr = sharing_info._internal_ptr;
210
211        // Handle zero-sized types specially
212        if sharing_info._internal_len == 0
213            || sharing_info._internal_layout_size == 0
214            || data_ptr.is_null()
215        {
216            let mut _dummy: [u8; 0] = [];
217            // Call destructor even for ZSTs (may have side effects)
218            (sharing_info.custom_destructor)(_dummy.as_ptr() as *mut c_void);
219        } else {
220            // Reconstruct the layout used during allocation
221            let layout = unsafe {
222                Layout::from_size_align_unchecked(
223                    sharing_info._internal_layout_size,
224                    sharing_info._internal_layout_align,
225                )
226            };
227
228            // Phase 1: Run the custom destructor
229            (sharing_info.custom_destructor)(data_ptr as *mut c_void);
230
231            // Phase 2: Deallocate the memory
232            unsafe {
233                alloc::alloc::dealloc(data_ptr as *mut u8, layout);
234            }
235        }
236    }
237}
238
239/// Debug-friendly snapshot of `RefCountInner` with non-atomic values.
240#[derive(Debug, Clone)]
241pub(crate) struct RefCountInnerDebug {
242    pub(crate) num_copies: usize,
243    pub(crate) num_refs: usize,
244    pub(crate) num_mutable_refs: usize,
245    pub(crate) _internal_len: usize,
246    pub(crate) _internal_layout_size: usize,
247    pub(crate) _internal_layout_align: usize,
248    pub(crate) type_id: u64,
249    pub(crate) type_name: AzString,
250    pub(crate) custom_destructor: usize,
251    /// Serialization function pointer (0 = not set)
252    pub(crate) serialize_fn: usize,
253    /// Deserialization function pointer (0 = not set)
254    pub(crate) deserialize_fn: usize,
255}
256
257impl RefCount {
258    /// Creates a new `RefCount` by boxing the metadata on the heap.
259    ///
260    /// # Safety
261    ///
262    /// Safe because we're creating a new allocation with `Box::new`,
263    /// then immediately leaking it with `into_raw` to get a stable pointer.
264    fn new(ref_count: RefCountInner) -> Self {
265        RefCount {
266            ptr: Box::into_raw(Box::new(ref_count)),
267            run_destructor: true,
268        }
269    }
270
271    /// Dereferences the raw pointer to access the metadata.
272    ///
273    /// # Safety
274    ///
275    /// Safe because:
276    /// - The pointer is created from `Box::into_raw`, so it's valid and properly aligned
277    /// - The lifetime is tied to `&self`, ensuring the pointer is still alive
278    /// - Reference counting ensures the data isn't freed while references exist
279    fn downcast(&self) -> &RefCountInner {
280        if self.ptr.is_null() {
281            panic!("[RefCount::downcast] FATAL: self.ptr is null!");
282        }
283        unsafe { &*self.ptr }
284    }
285
286    /// Creates a debug snapshot of the current reference counts.
287    ///
288    /// Loads all atomic values with `SeqCst` ordering to get a consistent view.
289    pub(crate) fn debug_get_refcount_copied(&self) -> RefCountInnerDebug {
290        let dc = self.downcast();
291        RefCountInnerDebug {
292            num_copies: dc.num_copies.load(AtomicOrdering::SeqCst),
293            num_refs: dc.num_refs.load(AtomicOrdering::SeqCst),
294            num_mutable_refs: dc.num_mutable_refs.load(AtomicOrdering::SeqCst),
295            _internal_len: dc._internal_len,
296            _internal_layout_size: dc._internal_layout_size,
297            _internal_layout_align: dc._internal_layout_align,
298            type_id: dc.type_id,
299            type_name: dc.type_name.clone(),
300            custom_destructor: dc.custom_destructor as usize,
301            serialize_fn: dc.serialize_fn,
302            deserialize_fn: dc.deserialize_fn,
303        }
304    }
305
306    /// Runtime check: can we create a shared borrow?
307    ///
308    /// Returns `true` if there are no active mutable borrows.
309    /// Multiple shared borrows can coexist (like `&T` in Rust).
310    ///
311    /// # Memory Ordering
312    ///
313    /// Uses `SeqCst` to ensure we see the most recent state from all threads.
314    /// If another thread just released a mutable borrow, we'll see it.
315    pub fn can_be_shared(&self) -> bool {
316        self.downcast()
317            .num_mutable_refs
318            .load(AtomicOrdering::SeqCst)
319            == 0
320    }
321
322    /// Runtime check: can we create a mutable borrow?
323    ///
324    /// Returns `true` only if there are ZERO active borrows of any kind.
325    /// This enforces Rust's exclusive mutability rule (like `&mut T`).
326    ///
327    /// # Memory Ordering
328    ///
329    /// Uses `SeqCst` to ensure we see all recent borrows from all threads.
330    /// Both counters must be checked atomically to prevent races.
331    pub fn can_be_shared_mut(&self) -> bool {
332        let info = self.downcast();
333        info.num_mutable_refs.load(AtomicOrdering::SeqCst) == 0
334            && info.num_refs.load(AtomicOrdering::SeqCst) == 0
335    }
336
337    /// Increments the shared borrow counter.
338    ///
339    /// Called when a `Ref<T>` is created. The `Ref::drop` will decrement it.
340    ///
341    /// # Memory Ordering
342    ///
343    /// `SeqCst` ensures this increment is visible to all threads before they
344    /// try to acquire a mutable borrow (which checks this counter).
345    pub fn increase_ref(&self) {
346        self.downcast()
347            .num_refs
348            .fetch_add(1, AtomicOrdering::SeqCst);
349    }
350
351    /// Decrements the shared borrow counter.
352    ///
353    /// Called when a `Ref<T>` is dropped, indicating the borrow is released.
354    ///
355    /// # Memory Ordering
356    ///
357    /// `SeqCst` ensures this decrement is immediately visible to other threads
358    /// waiting to acquire a mutable borrow.
359    pub fn decrease_ref(&self) {
360        self.downcast()
361            .num_refs
362            .fetch_sub(1, AtomicOrdering::SeqCst);
363    }
364
365    /// Increments the mutable borrow counter.
366    ///
367    /// Called when a `RefMut<T>` is created. Should only succeed when this
368    /// counter and `num_refs` are both 0.
369    ///
370    /// # Memory Ordering
371    ///
372    /// `SeqCst` ensures this increment is visible to all other threads,
373    /// blocking them from acquiring any borrow (shared or mutable).
374    pub fn increase_refmut(&self) {
375        self.downcast()
376            .num_mutable_refs
377            .fetch_add(1, AtomicOrdering::SeqCst);
378    }
379
380    /// Decrements the mutable borrow counter.
381    ///
382    /// Called when a `RefMut<T>` is dropped, releasing exclusive access.
383    ///
384    /// # Memory Ordering
385    ///
386    /// `SeqCst` ensures this decrement is immediately visible, allowing
387    /// other threads to acquire borrows.
388    pub fn decrease_refmut(&self) {
389        self.downcast()
390            .num_mutable_refs
391            .fetch_sub(1, AtomicOrdering::SeqCst);
392    }
393}
394
395/// RAII guard for a shared borrow of type `T` from a `RefAny`.
396///
397/// Similar to `std::cell::Ref`, this automatically decrements the borrow
398/// counter when dropped, ensuring borrows are properly released.
399///
400/// # Deref
401///
402/// Implements `Deref<Target = T>` so you can use it like `&T`.
403#[derive(Debug)]
404#[repr(C)]
405pub struct Ref<'a, T> {
406    ptr: &'a T,
407    sharing_info: RefCount,
408}
409
410impl<'a, T> Drop for Ref<'a, T> {
411    /// Automatically releases the shared borrow when the guard goes out of scope.
412    ///
413    /// # Safety
414    ///
415    /// Safe because `decrease_ref` uses atomic operations and is designed to be
416    /// called exactly once per `Ref` instance.
417    fn drop(&mut self) {
418        self.sharing_info.decrease_ref();
419    }
420}
421
422impl<'a, T> core::ops::Deref for Ref<'a, T> {
423    type Target = T;
424
425    fn deref(&self) -> &Self::Target {
426        self.ptr
427    }
428}
429
430/// RAII guard for a mutable borrow of type `T` from a `RefAny`.
431///
432/// Similar to `std::cell::RefMut`, this automatically decrements the mutable
433/// borrow counter when dropped, releasing exclusive access.
434///
435/// # Deref / DerefMut
436///
437/// Implements both `Deref` and `DerefMut` so you can use it like `&mut T`.
438#[derive(Debug)]
439#[repr(C)]
440pub struct RefMut<'a, T> {
441    ptr: &'a mut T,
442    sharing_info: RefCount,
443}
444
445impl<'a, T> Drop for RefMut<'a, T> {
446    /// Automatically releases the mutable borrow when the guard goes out of scope.
447    ///
448    /// # Safety
449    ///
450    /// Safe because `decrease_refmut` uses atomic operations and is designed to be
451    /// called exactly once per `RefMut` instance.
452    fn drop(&mut self) {
453        self.sharing_info.decrease_refmut();
454    }
455}
456
457impl<'a, T> core::ops::Deref for RefMut<'a, T> {
458    type Target = T;
459
460    fn deref(&self) -> &Self::Target {
461        &*self.ptr
462    }
463}
464
465impl<'a, T> core::ops::DerefMut for RefMut<'a, T> {
466    fn deref_mut(&mut self) -> &mut Self::Target {
467        self.ptr
468    }
469}
470
471/// Type-erased, reference-counted smart pointer with runtime borrow checking.
472///
473/// `RefAny` is similar to `Arc<RefCell<dyn Any>>`, providing:
474/// - Type erasure (stores any `'static` type)
475/// - Reference counting (clones share the same data)
476/// - Runtime borrow checking (enforces Rust's borrowing rules at runtime)
477/// - FFI compatibility (`#[repr(C)]` and C-compatible API)
478///
479/// # Thread Safety
480///
481/// - `Send`: Can be moved between threads (heap-allocated data, atomic counters)
482/// - `Sync`: Can be shared between threads (`downcast_ref/mut` require `&mut self`)
483///
484/// # Memory Safety
485///
486/// Fixed critical UB bugs in alignment, copy count, and pointer provenance.
487/// All operations are verified with Miri to ensure absence of undefined behavior.
488///
489/// # Usage
490///
491/// ```rust
492/// # use azul_core::refany::RefAny;
493/// let data = RefAny::new(42i32);
494/// let mut data_clone = data.clone(); // shares the same heap allocation
495///
496/// // Runtime-checked downcasting with type safety
497/// if let Some(value_ref) = data_clone.downcast_ref::<i32>() {
498///     assert_eq!(*value_ref, 42);
499/// };
500///
501/// // Runtime-checked mutable borrowing
502/// if let Some(mut value_mut) = data_clone.downcast_mut::<i32>() {
503///     *value_mut = 100;
504/// };
505/// ```
506#[derive(Debug, Hash, PartialEq, PartialOrd, Ord, Eq)]
507#[repr(C)]
508pub struct RefAny {
509    /// Shared metadata: reference counts, type info, destructor, AND data pointer.
510    ///
511    /// All `RefAny` clones point to the same `RefCountInner` via this field.
512    /// The data pointer is stored in RefCountInner so all clones see the same
513    /// pointer, even after replace_contents() is called.
514    ///
515    /// The `run_destructor` flag on `RefCount` controls whether dropping this
516    /// RefAny should decrement the reference count and potentially free memory.
517    pub sharing_info: RefCount,
518
519    /// Unique ID for this specific clone (root = 0, subsequent clones increment).
520    ///
521    /// Used to distinguish between the original and clones for debugging.
522    pub instance_id: u64,
523}
524
525impl_option!(
526    RefAny,
527    OptionRefAny,
528    copy = false,
529    [Debug, Hash, Clone, PartialEq, PartialOrd, Ord, Eq]
530);
531
532// SAFETY: RefAny is Send because:
533// - The data pointer points to heap memory (can be sent between threads)
534// - All shared state (RefCountInner) uses atomic operations
535// - No thread-local storage is used
536unsafe impl Send for RefAny {}
537
538// SAFETY: RefAny is Sync because:
539// - Methods on `&RefAny` (like `clone`, `get_type_id`) only use atomic operations or
540//   read immutable data, which is inherently thread-safe
541// - The runtime borrow checker (via `can_be_shared/shared_mut`) uses SeqCst atomics
542//
543// KNOWN ISSUE: `downcast_ref/mut` require `&mut self`, but clones of the same RefAny
544// are independent values that can each provide `&mut self` concurrently while sharing
545// the same `RefCountInner`. The check-then-increment in downcast_ref/mut is not atomic,
546// so concurrent borrows via different clones can race. See replace_contents() for the
547// correct compare_exchange pattern.
548unsafe impl Sync for RefAny {}
549
550impl RefAny {
551    /// Creates a new type-erased `RefAny` containing the given value.
552    ///
553    /// This is the primary way to construct a `RefAny` from Rust code.
554    ///
555    /// # Type Safety
556    ///
557    /// Stores the `TypeId` of `T` for runtime type checking during downcasts.
558    ///
559    /// # Memory Layout
560    ///
561    /// - Allocates memory on the heap with correct size (`size_of::<T>()`) and alignment
562    ///   (`align_of::<T>()`)
563    /// - Copies the value into the heap allocation
564    /// - Forgets the original value to prevent double-drop
565    ///
566    /// # Custom Destructor
567    ///
568    /// Creates a type-specific destructor that:
569    /// 1. Copies the data from heap back to stack
570    /// 2. Calls `mem::drop` to run `T`'s destructor
571    /// 3. The heap memory is freed separately in `RefAny::drop`
572    ///
573    /// This two-phase destruction ensures proper cleanup even for complex types.
574    ///
575    /// # Safety
576    ///
577    /// Safe because:
578    /// - `mem::forget` prevents double-drop of the original value
579    /// - Type `T` and destructor `<U>` are matched at compile time
580    /// - `ptr::copy_nonoverlapping` with count=1 copies exactly one `T`
581    ///
582    /// # Example
583    ///
584    /// ```rust
585    /// # use azul_core::refany::RefAny;
586    /// let mut data = RefAny::new(42i32);
587    /// let value = data.downcast_ref::<i32>().unwrap();
588    /// assert_eq!(*value, 42);
589    /// ```
590    pub fn new<T: 'static>(value: T) -> Self {
591        /// Type-specific destructor that properly drops the inner value.
592        ///
593        /// # Safety
594        ///
595        /// Safe to call ONLY with a pointer that was created by `RefAny::new<U>`.
596        /// The type `U` must match the original type `T`.
597        ///
598        /// # Why Copy to Stack?
599        ///
600        /// Rust's drop glue expects a value, not a pointer. We copy the data
601        /// to the stack so `mem::drop` can run the destructor properly.
602        ///
603        /// # Critical Fix
604        ///
605        /// The third argument to `copy_nonoverlapping` is the COUNT (1 element),
606        /// not the SIZE in bytes. Using `size_of::<U>()` here would copy
607        /// `size_of::<U>()` elements, causing buffer overflow.
608        extern "C" fn default_custom_destructor<U: 'static>(ptr: *mut c_void) {
609            use core::{mem, ptr};
610
611            unsafe {
612                // Allocate uninitialized stack space for one `U`
613                let mut stack_mem = mem::MaybeUninit::<U>::uninit();
614
615                // Copy 1 element of type U from heap to stack
616                ptr::copy_nonoverlapping(
617                    ptr as *const U,
618                    stack_mem.as_mut_ptr(),
619                    1, // CRITICAL: This is element count, not byte count!
620                );
621
622                // Take ownership and run the destructor
623                let stack_mem = stack_mem.assume_init();
624                mem::drop(stack_mem); // Runs U's Drop implementation
625            }
626        }
627
628        let type_name = ::core::any::type_name::<T>();
629        let type_id = Self::get_type_id_static::<T>();
630
631        let st = AzString::from_const_str(type_name);
632        let s = Self::new_c(
633            (&value as *const T) as *const c_void,
634            ::core::mem::size_of::<T>(),
635            ::core::mem::align_of::<T>(), // CRITICAL: Pass alignment to prevent UB
636            type_id,
637            st,
638            default_custom_destructor::<T>,
639            0, // serialize_fn: not set for Rust types by default
640            0, // deserialize_fn: not set for Rust types by default
641        );
642        ::core::mem::forget(value); // Prevent double-drop
643        s
644    }
645
646    /// C-ABI compatible function to create a `RefAny` from raw components.
647    ///
648    /// This is the low-level constructor used by FFI bindings (C, Python, etc.).
649    ///
650    /// # Parameters
651    ///
652    /// - `ptr`: Pointer to the value to store (will be copied)
653    /// - `len`: Size of the value in bytes (`size_of::<T>()`)
654    /// - `align`: Required alignment in bytes (`align_of::<T>()`)
655    /// - `type_id`: Unique identifier for the type (for downcast safety)
656    /// - `type_name`: Human-readable type name (for debugging)
657    /// - `custom_destructor`: Function to call when the last reference is dropped
658    /// - `serialize_fn`: Function pointer for JSON serialization (0 = not set)
659    /// - `deserialize_fn`: Function pointer for JSON deserialization (0 = not set)
660    ///
661    /// # Safety
662    ///
663    /// Caller must ensure:
664    /// - `ptr` points to valid data of size `len` with alignment `align`
665    /// - `type_id` uniquely identifies the type
666    /// - `custom_destructor` correctly drops the type at `ptr`
667    /// - `len` and `align` match the actual type's layout
668    /// - If `serialize_fn != 0`, it must be a valid function pointer of type
669    ///   `extern "C" fn(RefAny) -> Json`
670    /// - If `deserialize_fn != 0`, it must be a valid function pointer of type
671    ///   `extern "C" fn(Json) -> ResultRefAnyString`
672    ///
673    /// # Zero-Sized Types
674    ///
675    /// Special case: ZSTs use a null pointer but still track the type info
676    /// and call the destructor (which may have side effects even for ZSTs).
677    pub fn new_c(
678        // *const T
679        ptr: *const c_void,
680        // sizeof(T)
681        len: usize,
682        // alignof(T)
683        align: usize,
684        // unique ID of the type (used for type comparison when downcasting)
685        type_id: u64,
686        // name of the class such as "app::MyData", usually compiler- or macro-generated
687        type_name: AzString,
688        custom_destructor: extern "C" fn(*mut c_void),
689        // function pointer for JSON serialization (0 = not set)
690        serialize_fn: usize,
691        // function pointer for JSON deserialization (0 = not set)
692        deserialize_fn: usize,
693    ) -> Self {
694        use core::ptr;
695
696        // CRITICAL: Validate input pointer for non-ZST types
697        // A NULL pointer for a non-zero-sized type would cause UB when copying
698        if len > 0 && ptr.is_null() {
699            panic!(
700                "RefAny::new_c: NULL pointer passed for non-ZST type (size={}). \
701                This would cause undefined behavior. Type: {:?}",
702                len,
703                type_name.as_str()
704            );
705        }
706
707        // Special case: Zero-sized types
708        //
709        // Calling `alloc(Layout { size: 0, .. })` is UB, so we use a null pointer.
710        // The destructor is still called (it may have side effects even for ZSTs).
711        let (_internal_ptr, layout) = if len == 0 {
712            let _dummy: [u8; 0] = [];
713            (ptr::null_mut(), Layout::for_value(&_dummy))
714        } else {
715            // CRITICAL FIX: Use the caller-provided alignment, not alignment of [u8]
716            //
717            // Previous bug: `Layout::for_value(&[u8])` created align=1
718            // This caused unaligned references when downcasting to types like i32 (align=4)
719            //
720            // Fixed: `Layout::from_size_align(len, align)` respects the type's alignment
721            let layout = Layout::from_size_align(len, align).expect("Failed to create layout");
722
723            // Allocate heap memory with correct alignment
724            let heap_struct_as_bytes = unsafe { alloc::alloc::alloc(layout) };
725
726            // Handle allocation failure (aborts the program)
727            if heap_struct_as_bytes.is_null() {
728                alloc::alloc::handle_alloc_error(layout);
729            }
730
731            // Copy the data byte-by-byte to the heap
732            // SAFETY: Both pointers are valid, non-overlapping, and properly aligned
733            unsafe { ptr::copy_nonoverlapping(ptr as *const u8, heap_struct_as_bytes, len) };
734
735            (heap_struct_as_bytes, layout)
736        };
737
738        let ref_count_inner = RefCountInner {
739            _internal_ptr: _internal_ptr as *const c_void,
740            num_copies: AtomicUsize::new(1),       // This is the first instance
741            num_refs: AtomicUsize::new(0),         // No borrows yet
742            num_mutable_refs: AtomicUsize::new(0), // No mutable borrows yet
743            _internal_len: len,
744            _internal_layout_size: layout.size(),
745            _internal_layout_align: layout.align(),
746            type_id,
747            type_name,
748            custom_destructor,
749            serialize_fn,
750            deserialize_fn,
751        };
752
753        let sharing_info = RefCount::new(ref_count_inner);
754
755        Self {
756            sharing_info,
757            instance_id: 0, // Root instance
758        }
759    }
760
761    /// Returns the raw data pointer for FFI downcasting.
762    ///
763    /// This is used by the AZ_REFLECT macros in C/C++ to access the
764    /// type-erased data pointer for downcasting operations.
765    ///
766    /// # Safety
767    ///
768    /// The returned pointer must only be dereferenced after verifying
769    /// the type ID matches the expected type. Callers are responsible
770    /// for proper type safety checks.
771    pub fn get_data_ptr(&self) -> *const c_void {
772        self.sharing_info.downcast()._internal_ptr
773    }
774
775    /// Checks if this is the only `RefAny` instance with no active borrows.
776    ///
777    /// Returns `true` only if:
778    /// - `num_copies == 1` (no clones exist)
779    /// - `num_refs == 0` (no shared borrows active)
780    /// - `num_mutable_refs == 0` (no mutable borrows active)
781    ///
782    /// Useful for checking if you have exclusive ownership.
783    ///
784    /// # Memory Ordering
785    ///
786    /// Uses `SeqCst` to ensure a consistent view across all three counters.
787    pub(crate) fn has_no_copies(&self) -> bool {
788        self.sharing_info
789            .downcast()
790            .num_copies
791            .load(AtomicOrdering::SeqCst)
792            == 1
793            && self
794                .sharing_info
795                .downcast()
796                .num_refs
797                .load(AtomicOrdering::SeqCst)
798                == 0
799            && self
800                .sharing_info
801                .downcast()
802                .num_mutable_refs
803                .load(AtomicOrdering::SeqCst)
804                == 0
805    }
806
807    /// Attempts to downcast to a shared reference of type `U`.
808    ///
809    /// Returns `None` if:
810    /// - The stored type doesn't match `U` (type safety)
811    /// - A mutable borrow is already active (borrow checking)
812    /// - The pointer is null (ZST or uninitialized)
813    ///
814    /// # Type Safety
815    ///
816    /// Compares `type_id` at runtime before casting. This prevents casting
817    /// `*const c_void` to the wrong type, which would be immediate UB.
818    ///
819    /// # Borrow Checking
820    ///
821    /// Checks `can_be_shared()` to enforce Rust's borrowing rules:
822    /// - Multiple shared borrows are allowed
823    /// - Shared and mutable borrows cannot coexist
824    ///
825    /// # Safety
826    ///
827    /// The `unsafe` cast is safe because:
828    /// - Type ID check ensures `U` matches the stored type
829    /// - Memory was allocated with correct alignment for `U`
830    /// - Lifetime `'a` is tied to `&'a mut self`, preventing use-after-free
831    /// - Reference count is incremented atomically before returning
832    ///
833    /// # Why `&mut self`?
834    ///
835    /// Requires `&mut self` to prevent multiple threads from calling this
836    /// simultaneously on the same `RefAny`. The borrow checker enforces this.
837    /// Clones of the `RefAny` can call this independently (they share data
838    /// but have separate runtime borrow tracking).
839    #[inline]
840    pub fn downcast_ref<'a, U: 'static>(&'a mut self) -> Option<Ref<'a, U>> {
841        // Runtime type check: prevent downcasting to wrong type
842        let stored_type_id = self.get_type_id();
843        let target_type_id = Self::get_type_id_static::<U>();
844        let is_same_type = stored_type_id == target_type_id;
845
846        if !is_same_type {
847            return None;
848        }
849
850        // Runtime borrow check: ensure no mutable borrows exist
851        let can_be_shared = self.sharing_info.can_be_shared();
852        if !can_be_shared {
853            return None;
854        }
855
856        // Get data pointer from shared RefCountInner
857        let data_ptr = self.sharing_info.downcast()._internal_ptr;
858
859        // Null check: ZSTs or uninitialized
860        if data_ptr.is_null() {
861            return None;
862        }
863
864        // Increment shared borrow count atomically
865        self.sharing_info.increase_ref();
866
867        Some(Ref {
868            // SAFETY: Type check passed, pointer is non-null and properly aligned
869            ptr: unsafe { &*(data_ptr as *const U) },
870            sharing_info: self.sharing_info.clone(),
871        })
872    }
873
874    /// Attempts to downcast to a mutable reference of type `U`.
875    ///
876    /// Returns `None` if:
877    /// - The stored type doesn't match `U` (type safety)
878    /// - Any borrow is already active (borrow checking)
879    /// - The pointer is null (ZST or uninitialized)
880    ///
881    /// # Type Safety
882    ///
883    /// Compares `type_id` at runtime before casting, preventing UB.
884    ///
885    /// # Borrow Checking
886    ///
887    /// Checks `can_be_shared_mut()` to enforce exclusive mutability:
888    /// - No other borrows (shared or mutable) can be active
889    /// - This is Rust's `&mut T` rule, enforced at runtime
890    ///
891    /// # Safety
892    ///
893    /// The `unsafe` cast is safe because:
894    ///
895    /// - Type ID check ensures `U` matches the stored type
896    /// - Memory was allocated with correct alignment for `U`
897    /// - Borrow check ensures no other references exist
898    /// - Lifetime `'a` is tied to `&'a mut self`, preventing aliasing
899    /// - Mutable reference count is incremented atomically
900    ///
901    /// # Memory Ordering
902    ///
903    /// The `increase_refmut()` uses `SeqCst`, ensuring other threads see
904    /// this mutable borrow before they try to acquire any borrow.
905    #[inline]
906    pub fn downcast_mut<'a, U: 'static>(&'a mut self) -> Option<RefMut<'a, U>> {
907        // Runtime type check
908        let is_same_type = self.get_type_id() == Self::get_type_id_static::<U>();
909        if !is_same_type {
910            return None;
911        }
912
913        // Runtime exclusive borrow check
914        let can_be_shared_mut = self.sharing_info.can_be_shared_mut();
915        if !can_be_shared_mut {
916            return None;
917        }
918
919        // Get data pointer from shared RefCountInner
920        let data_ptr = self.sharing_info.downcast()._internal_ptr;
921
922        // Null check
923        if data_ptr.is_null() {
924            return None;
925        }
926
927        // Increment mutable borrow count atomically
928        self.sharing_info.increase_refmut();
929
930        Some(RefMut {
931            // SAFETY: Type and borrow checks passed, exclusive access guaranteed
932            ptr: unsafe { &mut *(data_ptr as *mut U) },
933            sharing_info: self.sharing_info.clone(),
934        })
935    }
936
937    /// Computes a runtime type ID from Rust's `TypeId`.
938    ///
939    /// Rust's `TypeId` is not `#[repr(C)]` and can't cross FFI boundaries.
940    /// This function converts it to a `u64` by treating it as a byte array.
941    ///
942    /// # Safety
943    ///
944    /// Safe because:
945    /// - `TypeId` is a valid type with a stable layout
946    /// - We only read from it, never write
947    /// - The slice lifetime is bounded by the function scope
948    ///
949    /// # Implementation
950    ///
951    /// Treats the `TypeId` as bytes and sums them with bit shifts to create
952    /// a unique (but not cryptographically secure) hash.
953    #[inline]
954    fn get_type_id_static<T: 'static>() -> u64 {
955        use core::{any::TypeId, mem};
956
957        let t_id = TypeId::of::<T>();
958
959        // SAFETY: TypeId is a valid type, we're only reading it
960        let struct_as_bytes = unsafe {
961            core::slice::from_raw_parts(
962                (&t_id as *const TypeId) as *const u8,
963                mem::size_of::<TypeId>(),
964            )
965        };
966
967        // Convert first 8 bytes to u64 using proper bit positions
968        struct_as_bytes
969            .into_iter()
970            .enumerate()
971            .take(8) // Only use first 8 bytes (64 bits fit in u64)
972            .map(|(s_pos, s)| (*s as u64) << (s_pos * 8))
973            .sum()
974    }
975
976    /// Checks if the stored type matches the given type ID.
977    pub fn is_type(&self, type_id: u64) -> bool {
978        self.sharing_info.downcast().type_id == type_id
979    }
980
981    /// Returns the stored type ID.
982    pub fn get_type_id(&self) -> u64 {
983        self.sharing_info.downcast().type_id
984    }
985
986    /// Returns the human-readable type name for debugging.
987    pub fn get_type_name(&self) -> AzString {
988        self.sharing_info.downcast().type_name.clone()
989    }
990
991    /// Returns the current reference count (number of `RefAny` clones sharing this data).
992    ///
993    /// This is useful for debugging and metadata purposes.
994    pub fn get_ref_count(&self) -> usize {
995        self.sharing_info
996            .downcast()
997            .num_copies
998            .load(AtomicOrdering::SeqCst)
999    }
1000
1001    /// Returns the serialize function pointer (0 = not set).
1002    /// 
1003    /// This is used for JSON serialization of RefAny contents.
1004    pub fn get_serialize_fn(&self) -> usize {
1005        self.sharing_info.downcast().serialize_fn
1006    }
1007
1008    /// Returns the deserialize function pointer (0 = not set).
1009    /// 
1010    /// This is used for JSON deserialization to create a new RefAny.
1011    pub fn get_deserialize_fn(&self) -> usize {
1012        self.sharing_info.downcast().deserialize_fn
1013    }
1014
1015    /// Sets the serialize function pointer.
1016    ///
1017    /// # Safety
1018    ///
1019    /// The caller must ensure the function pointer is valid and has the correct
1020    /// signature: `extern "C" fn(RefAny) -> Json`
1021    ///
1022    /// **Known issue:** `&mut self` is exclusive to this clone, not to the shared
1023    /// `RefCountInner`. Concurrent calls via different clones are a data race
1024    /// because `serialize_fn` is a plain `usize`, not atomic.
1025    pub fn set_serialize_fn(&mut self, serialize_fn: usize) {
1026        // FIXME: &mut self is exclusive to this clone only, not to the shared
1027        // RefCountInner — concurrent calls via different clones are a data race.
1028        let inner = self.sharing_info.ptr as *mut RefCountInner;
1029        unsafe {
1030            (*inner).serialize_fn = serialize_fn;
1031        }
1032    }
1033
1034    /// Sets the deserialize function pointer.
1035    ///
1036    /// # Safety
1037    ///
1038    /// The caller must ensure the function pointer is valid and has the correct
1039    /// signature: `extern "C" fn(Json) -> ResultRefAnyString`
1040    ///
1041    /// **Known issue:** `&mut self` is exclusive to this clone, not to the shared
1042    /// `RefCountInner`. Concurrent calls via different clones are a data race
1043    /// because `deserialize_fn` is a plain `usize`, not atomic.
1044    pub fn set_deserialize_fn(&mut self, deserialize_fn: usize) {
1045        // FIXME: &mut self is exclusive to this clone only, not to the shared
1046        // RefCountInner — concurrent calls via different clones are a data race.
1047        let inner = self.sharing_info.ptr as *mut RefCountInner;
1048        unsafe {
1049            (*inner).deserialize_fn = deserialize_fn;
1050        }
1051    }
1052
1053    /// Returns true if this RefAny supports JSON serialization.
1054    pub fn can_serialize(&self) -> bool {
1055        self.get_serialize_fn() != 0
1056    }
1057
1058    /// Returns true if this RefAny type supports JSON deserialization.
1059    pub fn can_deserialize(&self) -> bool {
1060        self.get_deserialize_fn() != 0
1061    }
1062
1063    /// Replaces the contents of this RefAny with a new value from another RefAny.
1064    ///
1065    /// This method:
1066    /// 1. Atomically acquires a mutable "lock" via compare_exchange
1067    /// 2. Calls the destructor on the old value
1068    /// 3. Deallocates the old memory
1069    /// 4. Copies the new value's memory
1070    /// 5. Updates metadata (type_id, type_name, destructor, serialize/deserialize fns)
1071    /// 6. Updates the shared _internal_ptr so ALL clones see the new data
1072    /// 7. Releases the lock
1073    ///
1074    /// Since all clones of a RefAny share the same `RefCountInner`, this change
1075    /// will be visible to ALL clones of this RefAny.
1076    ///
1077    /// # Returns
1078    ///
1079    /// - `true` if the replacement was successful
1080    /// - `false` if there are active borrows (would cause UB)
1081    ///
1082    /// # Thread Safety
1083    ///
1084    /// Uses compare_exchange to atomically acquire exclusive access, preventing
1085    /// any race condition between checking for borrows and modifying the data.
1086    ///
1087    /// # Safety
1088    ///
1089    /// Safe because:
1090    /// - We atomically acquire exclusive access before modifying
1091    /// - The old destructor is called before deallocation
1092    /// - Memory is properly allocated with correct alignment
1093    /// - All metadata is updated while holding the lock
1094    pub fn replace_contents(&mut self, new_value: RefAny) -> bool {
1095        use core::ptr;
1096
1097        let inner = self.sharing_info.ptr as *mut RefCountInner;
1098        
1099        // Atomically acquire exclusive access by setting num_mutable_refs to 1.
1100        // This uses compare_exchange to ensure no race condition:
1101        // - If num_mutable_refs is 0, set it to 1 (success)
1102        // - If num_mutable_refs is not 0, someone else has it (fail)
1103        // We also need to check num_refs == 0 atomically.
1104        let inner_ref = self.sharing_info.downcast();
1105        
1106        // First, try to acquire the mutable lock
1107        let mutable_lock_result = inner_ref.num_mutable_refs.compare_exchange(
1108            0,  // expected: no mutable refs
1109            1,  // desired: we take the mutable ref
1110            AtomicOrdering::SeqCst,
1111            AtomicOrdering::SeqCst,
1112        );
1113        
1114        if mutable_lock_result.is_err() {
1115            // Someone else has a mutable reference
1116            return false;
1117        }
1118        
1119        // Now check that there are no shared references
1120        // Note: We hold the mutable lock, so no new shared refs can be acquired
1121        if inner_ref.num_refs.load(AtomicOrdering::SeqCst) != 0 {
1122            // Release the lock and fail
1123            inner_ref.num_mutable_refs.store(0, AtomicOrdering::SeqCst);
1124            return false;
1125        }
1126        
1127        // We now have exclusive access - perform the replacement
1128        unsafe {
1129            // Get old layout info before we overwrite it
1130            let old_ptr = (*inner)._internal_ptr;
1131            let old_len = (*inner)._internal_len;
1132            let old_layout_size = (*inner)._internal_layout_size;
1133            let old_layout_align = (*inner)._internal_layout_align;
1134            let old_destructor = (*inner).custom_destructor;
1135
1136            // Step 1: Call destructor on old value (if non-ZST)
1137            if old_len > 0 && !old_ptr.is_null() {
1138                old_destructor(old_ptr as *mut c_void);
1139            }
1140
1141            // Step 2: Deallocate old memory (if non-ZST)
1142            if old_layout_size > 0 && !old_ptr.is_null() {
1143                let old_layout = Layout::from_size_align_unchecked(old_layout_size, old_layout_align);
1144                alloc::alloc::dealloc(old_ptr as *mut u8, old_layout);
1145            }
1146
1147            // Get new value's metadata
1148            let new_inner = new_value.sharing_info.downcast();
1149            let new_ptr = new_inner._internal_ptr;
1150            let new_len = new_inner._internal_len;
1151            let new_layout_size = new_inner._internal_layout_size;
1152            let new_layout_align = new_inner._internal_layout_align;
1153
1154            // Step 3: Allocate new memory and copy data
1155            let allocated_ptr = if new_len == 0 {
1156                ptr::null_mut()
1157            } else {
1158                let new_layout = Layout::from_size_align(new_len, new_layout_align)
1159                    .expect("Failed to create layout");
1160                let heap_ptr = alloc::alloc::alloc(new_layout);
1161                if heap_ptr.is_null() {
1162                    alloc::alloc::handle_alloc_error(new_layout);
1163                }
1164                // Copy data from new_value
1165                ptr::copy_nonoverlapping(
1166                    new_ptr as *const u8,
1167                    heap_ptr,
1168                    new_len,
1169                );
1170                heap_ptr
1171            };
1172
1173            // Step 4: Update the shared internal pointer in RefCountInner
1174            // All clones will see this new pointer!
1175            (*inner)._internal_ptr = allocated_ptr as *const c_void;
1176
1177            // Step 5: Update metadata in RefCountInner
1178            (*inner)._internal_len = new_len;
1179            (*inner)._internal_layout_size = new_layout_size;
1180            (*inner)._internal_layout_align = new_layout_align;
1181            (*inner).type_id = new_inner.type_id;
1182            (*inner).type_name = new_inner.type_name.clone();
1183            (*inner).custom_destructor = new_inner.custom_destructor;
1184            (*inner).serialize_fn = new_inner.serialize_fn;
1185            (*inner).deserialize_fn = new_inner.deserialize_fn;
1186        }
1187
1188        // Release the mutable lock
1189        self.sharing_info.downcast().num_mutable_refs.store(0, AtomicOrdering::SeqCst);
1190
1191        // Prevent new_value from running its destructor (we copied the data)
1192        core::mem::forget(new_value);
1193
1194        true
1195    }
1196}
1197
1198impl Clone for RefAny {
1199    /// Creates a new `RefAny` sharing the same heap-allocated data.
1200    ///
1201    /// This is cheap (just increments a counter) and is how multiple parts
1202    /// of the code can hold references to the same data.
1203    ///
1204    /// # Reference Counting
1205    ///
1206    /// Atomically increments `num_copies` with `SeqCst` ordering before
1207    /// creating the clone. This ensures all threads see the updated count
1208    /// before the clone can be used.
1209    ///
1210    /// # Instance ID
1211    ///
1212    /// Each clone gets a unique `instance_id` based on the current copy count.
1213    /// The original has `instance_id=0`, the first clone gets `1`, etc.
1214    ///
1215    /// # Memory Ordering
1216    ///
1217    /// The `fetch_add` followed by `load` both use `SeqCst`:
1218    /// - `fetch_add`: Ensures the increment is visible to all threads
1219    /// - `load`: Gets the updated value for the instance_id
1220    ///
1221    /// This prevents race conditions where two threads clone simultaneously
1222    /// and both see the same instance_id.
1223    ///
1224    /// # Safety
1225    ///
1226    /// Safe because:
1227    ///
1228    /// - Atomic operations prevent data races
1229    /// - The heap allocation remains valid (only freed when count reaches 0)
1230    /// - `run_destructor` is set to `true` for all clones
1231    fn clone(&self) -> Self {
1232        // Atomically increment the reference count
1233        let inner = self.sharing_info.downcast();
1234        let prev = inner.num_copies.fetch_add(1, AtomicOrdering::SeqCst);
1235
1236        let new_instance_id = (prev + 1) as u64;
1237
1238        Self {
1239            // Data pointer is now in RefCountInner, shared automatically
1240            sharing_info: RefCount {
1241                ptr: self.sharing_info.ptr, // Share the same metadata (and data pointer)
1242                run_destructor: true,       // This clone should decrement num_copies on drop
1243            },
1244            // Give this clone a unique ID based on the updated count
1245            instance_id: new_instance_id,
1246        }
1247    }
1248}
1249
1250impl Drop for RefAny {
1251    /// Empty drop implementation - all cleanup is handled by `RefCount::drop`.
1252    ///
1253    /// When a `RefAny` is dropped, its `sharing_info: RefCount` field is automatically
1254    /// dropped by Rust. The `RefCount::drop` implementation handles all cleanup:
1255    ///
1256    /// 1. Atomically decrements `num_copies` with `fetch_sub`
1257    /// 2. If the previous value was 1 (we're the last reference):
1258    ///    - Reclaims the `RefCountInner` via `Box::from_raw`
1259    ///    - Calls the custom destructor to run `T::drop()`
1260    ///    - Deallocates the heap memory with the stored layout
1261    ///
1262    /// # Why No Code Here?
1263    ///
1264    /// Previously, `RefAny::drop` handled cleanup, but this caused issues with the
1265    /// C API where `Ref<T>` and `RefMut<T>` guards (which clone the `RefCount`) need
1266    /// to keep the data alive even after the original `RefAny` is dropped.
1267    ///
1268    /// By moving all cleanup to `RefCount::drop`, we ensure that:
1269    /// - `RefAny::clone()` creates a `RefCount` with `run_destructor = true`
1270    /// - `AZ_REFLECT` macros create `Ref`/`RefMut` guards that clone `RefCount`
1271    /// - Each `RefCount` drop decrements the counter
1272    /// - Only the LAST drop (when `num_copies` was 1) cleans up memory
1273    ///
1274    /// See `RefCount::drop` for the full algorithm and safety documentation.
1275    fn drop(&mut self) {
1276        // RefCount::drop handles everything automatically.
1277        // The sharing_info field is dropped by Rust, triggering RefCount::drop.
1278    }
1279}