Skip to main content

RefAny

Struct RefAny 

Source
#[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 'static type)
  • 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/mut require &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: RefCount

Shared 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: u64

Unique ID for this specific clone (root = 0, subsequent clones increment).

Used to distinguish between the original and clones for debugging.

Implementations§

Source§

impl RefAny

Source

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:

  1. Copies the data from heap back to stack
  2. Calls mem::drop to run T’s destructor
  3. The heap memory is freed separately in RefAny::drop

This two-phase destruction ensures proper cleanup even for complex types.

§Safety

Safe because:

  • mem::forget prevents double-drop of the original value
  • Type T and destructor <U> are matched at compile time
  • ptr::copy_nonoverlapping with count=1 copies exactly one T
§Example
let mut data = RefAny::new(42i32);
let value = data.downcast_ref::<i32>().unwrap();
assert_eq!(*value, 42);
Source

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 dropped
  • serialize_fn: Function pointer for JSON serialization (0 = not set)
  • deserialize_fn: Function pointer for JSON deserialization (0 = not set)
§Safety

Caller must ensure:

  • ptr points to valid data of size len with alignment align
  • type_id uniquely identifies the type
  • custom_destructor correctly drops the type at ptr
  • len and align match the actual type’s layout
  • If serialize_fn != 0, it must be a valid function pointer of type extern "C" fn(RefAny) -> Json
  • If deserialize_fn != 0, it must be a valid function pointer of type extern "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).

Source

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.

Source

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.

Source

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 U matches the stored type
  • Memory was allocated with correct alignment for U
  • Lifetime 'a is 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).

Source

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 T rule, enforced at runtime
§Safety

The unsafe cast is safe because:

  • Type ID check ensures U matches the stored type
  • Memory was allocated with correct alignment for U
  • Borrow check ensures no other references exist
  • Lifetime 'a is 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.

Source

pub fn is_type(&self, type_id: u64) -> bool

Checks if the stored type matches the given type ID.

Source

pub fn get_type_id(&self) -> u64

Returns the stored type ID.

Source

pub fn get_type_name(&self) -> AzString

Returns the human-readable type name for debugging.

Source

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.

Source

pub fn get_serialize_fn(&self) -> usize

Returns the serialize function pointer (0 = not set).

This is used for JSON serialization of RefAny contents.

Source

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.

Source

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

Source

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

Source

pub fn can_serialize(&self) -> bool

Returns true if this RefAny supports JSON serialization.

Source

pub fn can_deserialize(&self) -> bool

Returns true if this RefAny type supports JSON deserialization.

Source

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:

  1. Atomically acquires a mutable “lock” via compare_exchange
  2. Calls the destructor on the old value
  3. Deallocates the old memory
  4. Copies the new value’s memory
  5. Updates metadata (type_id, type_name, destructor, serialize/deserialize fns)
  6. Updates the shared _internal_ptr so ALL clones see the new data
  7. 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
  • true if the replacement was successful
  • false if 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

Source§

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 threads
  • load: 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_destructor is set to true for all clones
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for RefAny

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Drop for RefAny

Source§

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:

  1. Atomically decrements num_copies with fetch_sub
  2. If the previous value was 1 (we’re the last reference):
    • Reclaims the RefCountInner via Box::from_raw
    • Calls the custom destructor to run T::drop()
    • Deallocates the heap memory with the stored layout
§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 a RefCount with run_destructor = true
  • AZ_REFLECT macros create Ref/RefMut guards that clone RefCount
  • Each RefCount drop decrements the counter
  • Only the LAST drop (when num_copies was 1) cleans up memory

See RefCount::drop for the full algorithm and safety documentation.

Source§

impl Hash for RefAny

Source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where H: Hasher, Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl Ord for RefAny

Source§

fn cmp(&self, other: &RefAny) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · Source§

fn max(self, other: Self) -> Self
where Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · Source§

fn min(self, other: Self) -> Self
where Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · Source§

fn clamp(self, min: Self, max: Self) -> Self
where Self: Sized,

Restrict a value to a certain interval. Read more
Source§

impl PartialEq for RefAny

Source§

fn eq(&self, other: &RefAny) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl PartialOrd for RefAny

Source§

fn partial_cmp(&self, other: &RefAny) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the <= operator. Read more
1.0.0 · Source§

fn gt(&self, other: &Rhs) -> bool

Tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · Source§

fn ge(&self, other: &Rhs) -> bool

Tests greater than or equal to (for self and other) and is used by the >= operator. Read more
Source§

impl Eq for RefAny

Source§

impl Send for RefAny

Source§

impl StructuralPartialEq for RefAny

Source§

impl Sync for RefAny

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> GetHash for T
where T: Hash,

Source§

fn get_hash(&self) -> u64

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.