#[repr(C)]pub struct RefAny {
pub sharing_info: RefCount,
pub instance_id: u64,
}Expand description
Type-erased, reference-counted smart pointer with runtime borrow checking.
RefAny is similar to Arc<RefCell<dyn Any>>, providing:
- Type erasure (stores any
'statictype) - Reference counting (clones share the same data)
- Runtime borrow checking (enforces Rust’s borrowing rules at runtime)
- FFI compatibility (
#[repr(C)]and C-compatible API)
§Thread Safety
Send: Can be moved between threads (heap-allocated data, atomic counters)Sync: Can be shared between threads (downcast_ref/mutrequire&mut self)
§Memory Safety
Fixed critical UB bugs in alignment, copy count, and pointer provenance (see REFANY_UB_FIXES.md). All operations are verified with Miri to ensure absence of undefined behavior.
§Usage
let data = RefAny::new(42i32);
let mut data_clone = data.clone(); // shares the same heap allocation
// Runtime-checked downcasting with type safety
if let Some(value_ref) = data_clone.downcast_ref::<i32>() {
assert_eq!(*value_ref, 42);
};
// Runtime-checked mutable borrowing
if let Some(mut value_mut) = data_clone.downcast_mut::<i32>() {
*value_mut = 100;
};Fields§
§sharing_info: RefCountShared metadata: reference counts, type info, destructor, AND data pointer.
All RefAny clones point to the same RefCountInner via this field.
The data pointer is stored in RefCountInner so all clones see the same
pointer, even after replace_contents() is called.
The run_destructor flag on RefCount controls whether dropping this
RefAny should decrement the reference count and potentially free memory.
instance_id: u64Unique ID for this specific clone (root = 0, subsequent clones increment).
Used to distinguish between the original and clones for debugging.
Implementations§
Source§impl RefAny
impl RefAny
Sourcepub fn new<T: 'static>(value: T) -> Self
pub fn new<T: 'static>(value: T) -> Self
Creates a new type-erased RefAny containing the given value.
This is the primary way to construct a RefAny from Rust code.
§Type Safety
Stores the TypeId of T for runtime type checking during downcasts.
§Memory Layout
- Allocates memory on the heap with correct size (
size_of::<T>()) and alignment (align_of::<T>()) - Copies the value into the heap allocation
- Forgets the original value to prevent double-drop
§Custom Destructor
Creates a type-specific destructor that:
- Copies the data from heap back to stack
- Calls
mem::dropto runT’s destructor - The heap memory is freed separately in
RefAny::drop
This two-phase destruction ensures proper cleanup even for complex types.
§Safety
Safe because:
mem::forgetprevents double-drop of the original value- Type
Tand destructor<U>are matched at compile time ptr::copy_nonoverlappingwith count=1 copies exactly oneT
§Example
let mut data = RefAny::new(42i32);
let value = data.downcast_ref::<i32>().unwrap();
assert_eq!(*value, 42);Sourcepub fn new_c(
ptr: *const c_void,
len: usize,
align: usize,
type_id: u64,
type_name: AzString,
custom_destructor: extern "C" fn(*mut c_void),
serialize_fn: usize,
deserialize_fn: usize,
) -> Self
pub fn new_c( ptr: *const c_void, len: usize, align: usize, type_id: u64, type_name: AzString, custom_destructor: extern "C" fn(*mut c_void), serialize_fn: usize, deserialize_fn: usize, ) -> Self
C-ABI compatible function to create a RefAny from raw components.
This is the low-level constructor used by FFI bindings (C, Python, etc.).
§Parameters
ptr: Pointer to the value to store (will be copied)len: Size of the value in bytes (size_of::<T>())align: Required alignment in bytes (align_of::<T>())type_id: Unique identifier for the type (for downcast safety)type_name: Human-readable type name (for debugging)custom_destructor: Function to call when the last reference is droppedserialize_fn: Function pointer for JSON serialization (0 = not set)deserialize_fn: Function pointer for JSON deserialization (0 = not set)
§Safety
Caller must ensure:
ptrpoints to valid data of sizelenwith alignmentaligntype_iduniquely identifies the typecustom_destructorcorrectly drops the type atptrlenandalignmatch the actual type’s layout- If
serialize_fn != 0, it must be a valid function pointer of typeextern "C" fn(RefAny) -> Json - If
deserialize_fn != 0, it must be a valid function pointer of typeextern "C" fn(Json) -> ResultRefAnyString
§Zero-Sized Types
Special case: ZSTs use a null pointer but still track the type info and call the destructor (which may have side effects even for ZSTs).
Sourcepub fn get_data_ptr(&self) -> *const c_void
pub fn get_data_ptr(&self) -> *const c_void
Returns the raw data pointer for FFI downcasting.
This is used by the AZ_REFLECT macros in C/C++ to access the type-erased data pointer for downcasting operations.
§Safety
The returned pointer must only be dereferenced after verifying the type ID matches the expected type. Callers are responsible for proper type safety checks.
Sourcepub fn has_no_copies(&self) -> bool
pub fn has_no_copies(&self) -> bool
Checks if this is the only RefAny instance with no active borrows.
Returns true only if:
num_copies == 1(no clones exist)num_refs == 0(no shared borrows active)num_mutable_refs == 0(no mutable borrows active)
Useful for checking if you have exclusive ownership.
§Memory Ordering
Uses SeqCst to ensure a consistent view across all three counters.
Sourcepub fn downcast_ref<'a, U: 'static>(&'a mut self) -> Option<Ref<'a, U>>
pub fn downcast_ref<'a, U: 'static>(&'a mut self) -> Option<Ref<'a, U>>
Attempts to downcast to a shared reference of type U.
Returns None if:
- The stored type doesn’t match
U(type safety) - A mutable borrow is already active (borrow checking)
- The pointer is null (ZST or uninitialized)
§Type Safety
Compares type_id at runtime before casting. This prevents casting
*const c_void to the wrong type, which would be immediate UB.
§Borrow Checking
Checks can_be_shared() to enforce Rust’s borrowing rules:
- Multiple shared borrows are allowed
- Shared and mutable borrows cannot coexist
§Safety
The unsafe cast is safe because:
- Type ID check ensures
Umatches the stored type - Memory was allocated with correct alignment for
U - Lifetime
'ais tied to&'a mut self, preventing use-after-free - Reference count is incremented atomically before returning
§Why &mut self?
Requires &mut self to prevent multiple threads from calling this
simultaneously on the same RefAny. The borrow checker enforces this.
Clones of the RefAny can call this independently (they share data
but have separate runtime borrow tracking).
Sourcepub fn downcast_mut<'a, U: 'static>(&'a mut self) -> Option<RefMut<'a, U>>
pub fn downcast_mut<'a, U: 'static>(&'a mut self) -> Option<RefMut<'a, U>>
Attempts to downcast to a mutable reference of type U.
Returns None if:
- The stored type doesn’t match
U(type safety) - Any borrow is already active (borrow checking)
- The pointer is null (ZST or uninitialized)
§Type Safety
Compares type_id at runtime before casting, preventing UB.
§Borrow Checking
Checks can_be_shared_mut() to enforce exclusive mutability:
- No other borrows (shared or mutable) can be active
- This is Rust’s
&mut Trule, enforced at runtime
§Safety
The unsafe cast is safe because:
- Type ID check ensures
Umatches the stored type - Memory was allocated with correct alignment for
U - Borrow check ensures no other references exist
- Lifetime
'ais tied to&'a mut self, preventing aliasing - Mutable reference count is incremented atomically
§Memory Ordering
The increase_refmut() uses SeqCst, ensuring other threads see
this mutable borrow before they try to acquire any borrow.
Sourcepub fn is_type(&self, type_id: u64) -> bool
pub fn is_type(&self, type_id: u64) -> bool
Checks if the stored type matches the given type ID.
Sourcepub fn get_type_id(&self) -> u64
pub fn get_type_id(&self) -> u64
Returns the stored type ID.
Sourcepub fn get_type_name(&self) -> AzString
pub fn get_type_name(&self) -> AzString
Returns the human-readable type name for debugging.
Sourcepub fn get_ref_count(&self) -> usize
pub fn get_ref_count(&self) -> usize
Returns the current reference count (number of RefAny clones sharing this data).
This is useful for debugging and metadata purposes.
Sourcepub fn get_serialize_fn(&self) -> usize
pub fn get_serialize_fn(&self) -> usize
Returns the serialize function pointer (0 = not set).
This is used for JSON serialization of RefAny contents.
Sourcepub fn get_deserialize_fn(&self) -> usize
pub fn get_deserialize_fn(&self) -> usize
Returns the deserialize function pointer (0 = not set).
This is used for JSON deserialization to create a new RefAny.
Sourcepub fn set_serialize_fn(&mut self, serialize_fn: usize)
pub fn set_serialize_fn(&mut self, serialize_fn: usize)
Sets the serialize function pointer.
§Safety
The caller must ensure the function pointer is valid and has the correct
signature: extern "C" fn(RefAny) -> Json
Sourcepub fn set_deserialize_fn(&mut self, deserialize_fn: usize)
pub fn set_deserialize_fn(&mut self, deserialize_fn: usize)
Sets the deserialize function pointer.
§Safety
The caller must ensure the function pointer is valid and has the correct
signature: extern "C" fn(Json) -> ResultRefAnyString
Sourcepub fn can_serialize(&self) -> bool
pub fn can_serialize(&self) -> bool
Returns true if this RefAny supports JSON serialization.
Sourcepub fn can_deserialize(&self) -> bool
pub fn can_deserialize(&self) -> bool
Returns true if this RefAny type supports JSON deserialization.
Sourcepub fn replace_contents(&mut self, new_value: RefAny) -> bool
pub fn replace_contents(&mut self, new_value: RefAny) -> bool
Replaces the contents of this RefAny with a new value from another RefAny.
This method:
- Atomically acquires a mutable “lock” via compare_exchange
- Calls the destructor on the old value
- Deallocates the old memory
- Copies the new value’s memory
- Updates metadata (type_id, type_name, destructor, serialize/deserialize fns)
- Updates the shared _internal_ptr so ALL clones see the new data
- Releases the lock
Since all clones of a RefAny share the same RefCountInner, this change
will be visible to ALL clones of this RefAny.
§Returns
trueif the replacement was successfulfalseif there are active borrows (would cause UB)
§Thread Safety
Uses compare_exchange to atomically acquire exclusive access, preventing any race condition between checking for borrows and modifying the data.
§Safety
Safe because:
- We atomically acquire exclusive access before modifying
- The old destructor is called before deallocation
- Memory is properly allocated with correct alignment
- All metadata is updated while holding the lock
Trait Implementations§
Source§impl Clone for RefAny
impl Clone for RefAny
Source§fn clone(&self) -> Self
fn clone(&self) -> Self
Creates a new RefAny sharing the same heap-allocated data.
This is cheap (just increments a counter) and is how multiple parts of the code can hold references to the same data.
§Reference Counting
Atomically increments num_copies with SeqCst ordering before
creating the clone. This ensures all threads see the updated count
before the clone can be used.
§Instance ID
Each clone gets a unique instance_id based on the current copy count.
The original has instance_id=0, the first clone gets 1, etc.
§Memory Ordering
The fetch_add followed by load both use SeqCst:
fetch_add: Ensures the increment is visible to all threadsload: Gets the updated value for the instance_id
This prevents race conditions where two threads clone simultaneously and both see the same instance_id.
§Safety
Safe because:
- Atomic operations prevent data races
- The heap allocation remains valid (only freed when count reaches 0)
run_destructoris set totruefor all clones
1.0.0 · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Drop for RefAny
impl Drop for RefAny
Source§fn drop(&mut self)
fn drop(&mut self)
Empty drop implementation - all cleanup is handled by RefCount::drop.
When a RefAny is dropped, its sharing_info: RefCount field is automatically
dropped by Rust. The RefCount::drop implementation handles all cleanup:
- Atomically decrements
num_copieswithfetch_sub - If the previous value was 1 (we’re the last reference):
- Reclaims the
RefCountInnerviaBox::from_raw - Calls the custom destructor to run
T::drop() - Deallocates the heap memory with the stored layout
- Reclaims the
§Why No Code Here?
Previously, RefAny::drop handled cleanup, but this caused issues with the
C API where Ref<T> and RefMut<T> guards (which clone the RefCount) need
to keep the data alive even after the original RefAny is dropped.
By moving all cleanup to RefCount::drop, we ensure that:
RefAny::clone()creates aRefCountwithrun_destructor = trueAZ_REFLECTmacros createRef/RefMutguards that cloneRefCount- Each
RefCountdrop decrements the counter - Only the LAST drop (when
num_copieswas 1) cleans up memory
See RefCount::drop for the full algorithm and safety documentation.
Source§impl Ord for RefAny
impl Ord for RefAny
Source§impl PartialOrd for RefAny
impl PartialOrd for RefAny
impl Eq for RefAny
impl Send for RefAny
impl StructuralPartialEq for RefAny
impl Sync for RefAny
Auto Trait Implementations§
impl Freeze for RefAny
impl RefUnwindSafe for RefAny
impl Unpin for RefAny
impl UnwindSafe for RefAny
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more