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}