deorbit-core 0.1.4

A dependency injection framework for Rust
Documentation
use crate::runtime::meta::TypeMeta;
use std::mem;
use std::ptr;
use std::ptr::null;
use std::sync::Arc;

/// Allows storing heterogeneous data in the same collection.
#[derive(Debug)]
pub struct ErasedArc {
    type_id: TypeMeta,
    // Here Arc might have a size of 16 bytes hence not safe to be stored
    // as a plain Arc because fat pointers don't have a guaranteed layout
    data: [*const (); 2],
    // Used to decrement reference count for an arc
    dec_fn: unsafe fn([*const (); 2]),
    // Used to increment reference count for an arc
    inc_fn: unsafe fn([*const (); 2]),
}

unsafe impl Send for ErasedArc {}
unsafe impl Sync for ErasedArc {}

impl ErasedArc {
    pub fn from_instance<T>(instance: T) -> Self
    where
        T: Send + Sync + 'static,
    {
        Self::from(Arc::new(instance))
    }

    pub fn from<T>(instance: Arc<T>) -> Self
    where
        T: ?Sized + Send + Sync + 'static,
    {
        Self {
            type_id: TypeMeta::of::<T>(),
            data: unsafe {
                let raw = Arc::into_raw(instance);
                let mut data = [null(); 2];

                ptr::copy_nonoverlapping(
                    &raw as *const _ as *const u8, // Important to take a reference to the pointer itself
                    data.as_mut_ptr() as *mut u8,
                    size_of_val(&raw),
                );

                data
            },
            dec_fn: |x| unsafe {
                let ptr = Self::cast_ptr::<T>(&x);

                Arc::decrement_strong_count(ptr);
            },
            inc_fn: |x| unsafe {
                let ptr = Self::cast_ptr::<T>(&x);

                Arc::increment_strong_count(ptr);
            },
        }
    }

    pub fn ty(&self) -> TypeMeta {
        self.type_id
    }

    pub fn coerce<T>(&self) -> Option<Arc<T>>
    where
        T: ?Sized + Send + Sync + 'static,
    {
        if self.type_id == TypeMeta::of::<T>() {
            let coerced = unsafe {
                let ptr = Self::cast_ptr::<T>(&self.data);

                Arc::increment_strong_count(ptr);
                Arc::from_raw(ptr)
            };

            Some(coerced)
        } else {
            None
        }
    }

    pub fn as_ptr(arc: &Self) -> *const () {
        unsafe { Self::cast_ptr(&arc.data) }
    }

    unsafe fn cast_ptr<T: ?Sized>(from: &[*const (); 2]) -> *const T {
        unsafe { mem::transmute_copy(from) }
    }
}

impl Clone for ErasedArc {
    fn clone(&self) -> Self {
        unsafe {
            (self.inc_fn)(self.data);
        }

        Self {
            type_id: self.type_id,
            data: self.data,
            dec_fn: self.dec_fn,
            inc_fn: self.inc_fn,
        }
    }
}

impl Drop for ErasedArc {
    fn drop(&mut self) {
        unsafe {
            (self.dec_fn)(self.data);
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::sync::Arc;

    #[test]
    fn test_coercion() {
        let erased = ErasedArc::from_instance(42);
        let coerced = erased.coerce::<i32>().unwrap();

        assert_eq!(*coerced, 42);
    }

    #[test]
    fn test_unsized() {
        trait MyTrait {
            fn get(&self) -> i32;
        }
        impl MyTrait for i32 {
            fn get(&self) -> i32 {
                *self
            }
        }

        let arc: Arc<dyn MyTrait + Send + Sync> = Arc::new(100);

        let erased = ErasedArc::from(arc);
        let coerced = erased.coerce::<dyn MyTrait + Send + Sync>().unwrap();

        assert_eq!(coerced.get(), 100);
    }

    #[test]
    fn test_clone() {
        let arc = Arc::new(10);
        let erased = ErasedArc::from(arc.clone());

        assert_eq!(Arc::strong_count(&arc), 2);

        {
            let _cloned = erased.clone();
            assert_eq!(Arc::strong_count(&arc), 3);
        } // _cloned drops here

        assert_eq!(Arc::strong_count(&arc), 2);
    }
}