crusty_traits

Crates.iocrusty_traits
lib.rscrusty_traits
version0.1.0
created_at2025-10-24 04:02:54.453374+00
updated_at2025-10-24 04:02:54.453374+00
descriptionA crate that creates a macro and supporting code to allow for traits to be FFI-safe using C ABI
homepagehttps://github.com/n1ght-hunter/crusty_traits
repositoryhttps://github.com/n1ght-hunter/crusty_traits
max_upload_size
id1897976
size45,576
Night_Hunter (n1ght-hunter)

documentation

https://docs.rs/crusty_traits

README

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

[dependencies]  
crusty_traits = "0.1"

Then in your code

use crusty_traits::prelude::*;

#[crusty_trait]
pub trait MyTrait {
    fn method1(&self);
    fn method2(&mut self, value: i32) -> i32;
}
Roughly expands to the following
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,
            )
        }
    }
}

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.

Commit count: 0

cargo fmt