crusty_traits 0.1.0

A crate that creates a macro and supporting code to allow for traits to be FFI-safe using C ABI
Documentation
# Crusty Traits 

## C <-> Rust Traits


A crate that creates a macro and supporting code to allow for traits to be FFI-safe using C ABI.
> [!WARNING]
> This crate uses unsafe code and may be unsound if used incorrectly. Use at your own risk.
> If any issues are found please open an issue or a PR.

## Usage

Add the following to your Cargo.toml

```toml
[dependencies]  
crusty_traits = "0.1"
```

Then in your code
```rust
use crusty_traits::prelude::*;

#[crusty_trait]

pub trait MyTrait {
    fn method1(&self);
    fn method2(&mut self, value: i32) -> i32;
}
```

<details>

<summary>Roughly expands to the following</summary>

```rust
use crusty_traits::prelude::*;
pub trait MyTrait {
    fn method1(&self);
    fn method2(&mut self, value: i32) -> i32;
}
#[repr(C)]

///A repr C vtable for the trait MyTrait
pub struct MyTraitVTable {
    pub method1: unsafe extern "C" fn(CRef<MyTraitVTable>),
    pub method2: unsafe extern "C" fn(CRefMut<MyTraitVTable>, i32) -> i32,
    ///A function pointer to the drop function for the trait
    pub drop: unsafe extern "C" fn(CRefMut<MyTraitVTable>),
}
impl CDrop for MyTraitVTable {
    fn drop(repr: CRefMut<Self>) {
        unsafe { (repr.get_vtable().drop)(repr) }
    }
}
impl MyTraitVTable {
    /// Creates a new vtable for the type GEN that implements the trait
    pub fn new_boxed<GEN: MyTrait + 'static>(input: GEN) -> CRepr<MyTraitVTable> {
        let vtable = MyTraitVTable::create_vtable::<GEN>();
        CRepr::new_boxed(vtable, input)
    }
    /// Creates a new vtable for the type GEN then store in a static variable in the heap
    pub fn create_vtable<GEN: MyTrait + 'static>() -> &'static MyTraitVTable {
        static FN_MAP: std::sync::LazyLock<
            std::sync::Mutex<
                std::collections::HashMap<
                    std::any::TypeId,
                    &'static (dyn std::any::Any + Send + Sync),
                >,
            >,
        > = std::sync::LazyLock::new(|| std::sync::Mutex::new(
            std::collections::HashMap::new(),
        ));
        let type_id = std::any::TypeId::of::<GEN>();
        let mut map = FN_MAP.lock().unwrap();
        let entry = map
            .entry(type_id)
            .or_insert_with(|| {
                let vtable = Box::new(MyTraitVTable {
                    method1: {
                        unsafe extern "C" fn method1<GEN: MyTrait>(
                            arg0: CRef<MyTraitVTable>,
                        ) {
                            #[allow(unsafe_code)]
                            unsafe { GEN::method1(&*(arg0.as_ptr() as *const GEN)) }
                        }
                        method1::<GEN>
                    },
                    method2: {
                        unsafe extern "C" fn method2<GEN: MyTrait>(
                            arg0: CRefMut<MyTraitVTable>,
                            arg1: i32,
                        ) -> i32 {
                            #[allow(unsafe_code)]
                            unsafe {
                                GEN::method2(&mut *(arg0.as_ptr() as *mut GEN), arg1)
                            }
                        }
                        method2::<GEN>
                    },
                    drop: {
                        unsafe extern "C" fn drop<GEN: MyTrait>(
                            arg_0: CRefMut<MyTraitVTable>,
                        ) {
                            #[allow(unsafe_code)]
                            unsafe {
                                ::core::mem::drop(
                                    Box::from_raw(arg_0.as_ptr() as *mut GEN),
                                );
                            }
                        }
                        drop::<GEN>
                    },
                });
                Box::leak(vtable)
            });
        entry.downcast_ref().unwrap()
    }
}
impl MyTrait for CRepr<MyTraitVTable> {
    fn method1(&self) {
        #[allow(unsafe_code)] 
        unsafe { (self.get_vtable().method1)(self.as_cref()) }
    }
    fn method2(&mut self, value: i32) -> i32 {
        #[allow(unsafe_code)]
        unsafe { (self.get_vtable().method2)(self.as_cref_mut(), value) }
    }
}
impl<GEN> MyTrait for CRepr<GEN>
where
    GEN: AsVTable<&'static MyTraitVTable> + CDrop,
{
    fn method1(&self) {
        let methods: &'static MyTraitVTable = self.as_vtable();
        #[allow(unsafe_code)]
        unsafe {
            (methods
                .method1)(
                self.as_cref_with_methods(std::ptr::NonNull::from(methods)),
            )
        }
    }
    fn method2(&mut self, value: i32) -> i32 {
        let methods: &'static MyTraitVTable = self.as_vtable();
        #[allow(unsafe_code)]
        unsafe {
            (methods
                .method2)(
                self.as_cref_mut_with_methods(std::ptr::NonNull::from(methods)),
                value,
            )
        }
    }
}

```
</details>

## Crate Details

This crate provides a macro `crusty_trait` that generates the necessary boilerplate code to create a C-compatible vtable for a given Rust trait.
This allows Rust traits to be used across FFI boundaries, making it easier to use Rust shared libraries or plugins in C or other languages that can interface with C.
Each trait that is annotated with `crusty_trait` will have a corresponding vtable struct generated, along with implementations for `CRepr` and `CDrop` to manage the memory and lifecycle of the trait objects.
The generated vtable struct will contain function pointers for each method in the trait, as well as a drop function to properly clean up the trait object when it is no longer needed. 
The trait is also implemented for `CRepr<MyTraitVTable>` and any `CRepr<GEN>` where `GEN` implements `AsVTable<&'static MyTraitVTable>`(used for super/sub traits) and `CDrop`, allowing for seamless usage of the trait across FFI boundaries in Rust code.