diff --git a/README.md b/README.md index ded6865..3e2b9dd 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -# snips-utils-rs +# ffi_convert + +**A collection of utilities (functions, traits, data structures, etc ...) to ease conversion between Rust and C-compatible data structures.** ## License diff --git a/ffi-utils-derive/src/lib.rs b/ffi-utils-derive/src/lib.rs index e515787..1a6e86b 100644 --- a/ffi-utils-derive/src/lib.rs +++ b/ffi-utils-derive/src/lib.rs @@ -1,3 +1,5 @@ +//! This crate provides ffi_convert derive macros for CReprOf, AsRust and CDrop traits. + extern crate proc_macro; mod asrust; diff --git a/ffi-utils/src/conversions.rs b/ffi-utils/src/conversions.rs index 859b8fb..6676e40 100644 --- a/ffi-utils/src/conversions.rs +++ b/ffi-utils/src/conversions.rs @@ -1,5 +1,8 @@ use failure::{ensure, format_err, Error, ResultExt}; +/// A macro to convert a `std::String` to a C-compatible representation : a raw pointer to libc::c_char. +/// After calling this function, the caller is responsible for releasing the memory. +/// The [`take_back_c_string!`] macro can be used for releasing the memory. #[macro_export] macro_rules! convert_to_c_string { ($string:expr) => { @@ -7,6 +10,10 @@ macro_rules! convert_to_c_string { }; } +/// A macro to convert a `std::String` to a C-compatible representation a raw pointer to libc::c_char +/// wrapped in a Result enum. +/// After calling this function, the caller is responsible for releasing the memory. +/// The [`take_back_c_string!`] macro can be used for releasing the memory. #[macro_export] macro_rules! convert_to_c_string_result { ($string:expr) => { @@ -17,6 +24,9 @@ macro_rules! convert_to_c_string_result { }; } +/// A macro to convert a `Vec` to a C-compatible representation : a raw pointer to a CStringArray +/// After calling this function, the caller is responsible for releasing the memory. +/// The [`take_back_c_string_array!`] macro can be used for releasing the memory. #[macro_export] macro_rules! convert_to_c_string_array { ($string_vec:expr) => {{ @@ -25,6 +35,9 @@ macro_rules! convert_to_c_string_array { }}; } +/// A macro to convert a `Vec` to a C-compatible representation : a raw pointer to a CStringArray +/// After calling this function, the caller is responsible for releasing the memory. +/// The [`take_back_c_string_array!`] macro can be used for releasing the memory. #[macro_export] macro_rules! convert_to_nullable_c_string_array { ($opt:expr) => { @@ -36,6 +49,8 @@ macro_rules! convert_to_nullable_c_string_array { }; } +/// A macro to convert an `Option` to a C-compatible representation : a raw pointer to libc::c_char if the Option enum is of variant Some, +/// or a null pointer if the Option enum is of variant None. #[macro_export] macro_rules! convert_to_nullable_c_string { ($opt:expr) => { @@ -47,6 +62,7 @@ macro_rules! convert_to_nullable_c_string { }; } +/// Retakes the ownership of the memory pointed to by a raw pointer to a libc::c_char #[macro_export] macro_rules! take_back_c_string { ($pointer:expr) => {{ @@ -55,6 +71,7 @@ macro_rules! take_back_c_string { }}; } +/// Retakes the ownership of the memory pointed to by a raw pointer to a libc::c_char, checking first if the pointer is not null. #[macro_export] macro_rules! take_back_nullable_c_string { ($pointer:expr) => { @@ -64,6 +81,7 @@ macro_rules! take_back_nullable_c_string { }; } +/// Retakes the ownership of the memory storing an array of C-compatible strings #[macro_export] macro_rules! take_back_c_string_array { ($pointer:expr) => {{ @@ -72,6 +90,7 @@ macro_rules! take_back_c_string_array { }}; } +/// Retakes the ownership of the memory storing an array of C-compatible strings, checking first if the provided pointer is not null. #[macro_export] macro_rules! take_back_nullable_c_string_array { ($pointer:expr) => { @@ -81,6 +100,7 @@ macro_rules! take_back_nullable_c_string_array { }; } +/// Unsafely creates an owned string from a pointer to a nul-terminated array of bytes. #[macro_export] macro_rules! create_rust_string_from { ($pointer:expr) => {{ @@ -92,6 +112,7 @@ macro_rules! create_rust_string_from { }}; } +/// Unsafely creates an optional owned string from a pointer to a nul-terminated array of bytes. #[macro_export] macro_rules! create_optional_rust_string_from { ($pointer:expr) => { @@ -102,6 +123,7 @@ macro_rules! create_optional_rust_string_from { }; } +/// Unsafely creates an array of owned string from a pointer to a CStringArray. #[macro_export] macro_rules! create_rust_vec_string_from { ($pointer:expr) => {{ @@ -110,6 +132,7 @@ macro_rules! create_rust_vec_string_from { }}; } +/// Unsafely creates an optional array of owned string from a pointer to a CStringArray. #[macro_export] macro_rules! create_optional_rust_vec_string_from { ($pointer:expr) => { @@ -138,6 +161,7 @@ macro_rules! impl_c_repr_of_for { }; } +/// implements a noop implementation of the CDrop trait for a given type. macro_rules! impl_c_drop_for { ($typ:ty) => { impl CDrop for $typ { @@ -210,15 +234,18 @@ pub trait RawPointerConverter: Sized { } } +/// Trait to create borrowed references to type T, from a raw pointer to a T pub trait RawBorrow { unsafe fn raw_borrow<'a>(input: *const T) -> Result<&'a Self, Error>; } +/// Trait to create mutable borrowed references to type T, from a raw pointer to a T pub trait RawBorrowMut { unsafe fn raw_borrow_mut<'a>(input: *mut T) -> Result<&'a mut Self, Error>; } /// TODO custom derive instead of generic impl, this would prevent CString from having 2 impls... +/// Trait representing conversion operations from and to owned type T to a raw pointer to T impl RawPointerConverter for T { fn into_raw_pointer(self) -> *const T { Box::into_raw(Box::new(self)) as _ @@ -233,6 +260,7 @@ impl RawPointerConverter for T { } } +/// Trait that allows obtaining a borrowed reference to a type T from a raw pointer to T impl RawBorrow for T { unsafe fn raw_borrow<'a>(input: *const T) -> Result<&'a Self, Error> { input @@ -241,6 +269,7 @@ impl RawBorrow for T { } } +/// Trait that allows obtaining a mutable borrowed reference to a type T from a raw pointer to T impl RawBorrowMut for T { unsafe fn raw_borrow_mut<'a>(input: *mut T) -> Result<&'a mut Self, Error> { input diff --git a/ffi-utils/src/lib.rs b/ffi-utils/src/lib.rs index 04649be..3f305c4 100644 --- a/ffi-utils/src/lib.rs +++ b/ffi-utils/src/lib.rs @@ -1,3 +1,172 @@ +//! A collection of utilities (functions, traits, data structures, etc ...) to ease conversion between Rust and C-compatible data structures. +//! +//! Through two **conversion traits**, **`CReprOf`** and **`AsRust`**, this crate provides a framework to convert idiomatic Rust structs to C-compatible structs that can pass through an FFI boundary, and conversely. +//! They ensure that the developper uses best practices when performing the conversion in both directions (ownership-wise). +//! +//! The crate also provides a collection of useful utility functions to perform conversions of types. +//! It goes hand in hand with the `ffi-convert-derive` crate as it provides an **automatic derivation** of the `CReprOf` and `AsRust` trait. +//! +//! # Usage +//! When dealing with an FFI frontier, the general philosophy of the crate is : +//! - When receiving pointers to structs created by C code, the struct is immediately converted to an owned, idiomatic Rust struct through the use of the `AsRust` trait. +//! - To send an idiomatic, owned Rust struct to C code, the struct is converted to C-compatible representation using the CReprOf trait. +//! +//! ## Example +//! +//! We want to be able to convert a **`Pizza`** Rust struct that has an idiomatic representation to a **`CPizza`** Rust struct that has a C-compatible representation in memory. +//! We start by definining the fields of the `Pizza` struct : +//! ``` +//! # struct Topping {}; +//! # struct Sauce {}; +//! pub struct Pizza { +//! pub name: String, +//! pub toppings: Vec, +//! pub base: Option, +//! pub weight: f32, +//! } +//!``` +//! +//! We then create the C-compatible struct by [mapping](#types-representations-mapping) idiomatic Rust types to C-compatible types : +//! ``` +//! # use ffi_utils::CArray; +//! # struct CTopping {}; +//! # struct CSauce {}; +//! #[repr(C)] +//! pub struct CPizza { +//! pub name: *const libc::c_char, +//! pub toppings: *const CArray, +//! pub base: *const CSauce, +//! pub weight: libc::c_float, +//! } +//! ``` +//! +//! This crate provides two traits that are useful for converting between Pizza to CPizza and conversely. +//! +//! ```ignore +//! CPizza::c_repr_of(pizza) +//! <=================| +//! +//! CPizza Pizza +//! +//! |=================> +//! cpizza.as_rust() +//! +//! ``` +//! Instead of manually writing the body of the conversion traits, we can derive them : +//! +//! ``` +//! # use ffi_utils::{CReprOf, AsRust, CDrop}; +//! # use ffi_utils::CArray; +//! # use ffi_utils::RawBorrow; +//! # struct Sauce {}; +//! # #[derive(CReprOf, AsRust, CDrop)] +//! # #[target_type(Sauce)] +//! # struct CSauce {}; +//! # struct Topping {}; +//! # #[derive(CReprOf, AsRust, CDrop)] +//! # #[target_type(Topping)] +//! # struct CTopping {}; +//! # +//! # struct Pizza { +//! # name: String, +//! # toppings: Vec, +//! # base: Sauce, +//! # weight: f32 +//! # }; +//! use libc::{c_char, c_float}; +//! +//! #[repr(C)] +//! #[derive(CReprOf, AsRust, CDrop)] +//! #[target_type(Pizza)] +//! pub struct CPizza { +//! pub name: *const c_char, +//! pub toppings: *const CArray, +//! pub base: *const CSauce, +//! pub weight: c_float, +//! } +//! ``` +//! +//! You may have noticed that you have to derive the CDrop trait. +//! The CDrop trait needs to be implemented on every C-compatible struct that require manual resource management. +//! The release of those resources should be done in the drop method of the CDrop trait. +//! +//! You can now pass the `CPizza` struct through your FFI boundary ! +//! + +//! ## Types representations mapping +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//!
C typeRust typeC-compatible Rust type
const char*String*const libc::c_char
const TOption*const T (with #[nullable] field annotation)
CArrayTVecCArray
+//! +//! ```ignore +//! typedef struct { +//! const T *values; // Pointer to the value of the list +//! int32_t size; // Number of T values in the list +//! } CArrayT; +//! ``` + +//! ## The CReprOf trait + +//! The `CReprOf` trait allows to create a C-compatible representation of the reciprocal idiomatic Rust struct by consuming the latter. + +//! ``` +//! # use ffi_utils::{Error, CDrop}; +//! pub trait CReprOf: Sized + CDrop { +//! fn c_repr_of(input: T) -> Result; +//! } +//! ``` + +//! This shows that the struct implementing it is a `repr(C)` compatible view of the parametrized +//! type and can be created from an object of this type. + +//! ## The AsRust trait + +//! > When trying to convert a `repr(C)` struct that originated from C, the philosophy is to immediately convert +//! > the struct to an **owned** idiomatic representation of the struct via the AsRust trait. + +//! The `AsRust` trait allows to create an idiomatic Rust struct from a C-compatible struct : + +//! ``` +//! # use ffi_utils::{Error, CDrop}; +//! pub trait AsRust { +//! fn as_rust(&self) -> Result; +//! } +//! ``` + +//! This shows that the struct implementing it is a `repr(C)` compatible view of the parametrized +//! type and that an instance of the parametrized type can be created form this struct. + +//! ## The CDrop trait + +//! A Trait showing that the `repr(C)` compatible view implementing it can free up its part of memory that are not +//! managed by Rust drop mechanism. + +//! ## Caveats with derivation of CReprOf and AsRust traits +//! pub use ffi_utils_derive::*; mod conversions; diff --git a/ffi-utils/src/types.rs b/ffi-utils/src/types.rs index c902345..b01e3d5 100644 --- a/ffi-utils/src/types.rs +++ b/ffi-utils/src/types.rs @@ -1,3 +1,6 @@ +//! This module contains definitions of utility types that implement the [`CReprOf`], [`AsRust`], and [`CDrop`] traits. +//! + use std::ffi::CString; use std::ptr::null; @@ -7,7 +10,7 @@ use crate::conversions::*; use crate::convert_to_c_string_result; use crate::create_rust_string_from; -/// Used as a return type of functions that can encounter errors +/// Used as a return type for functions that can encounter errors #[repr(C)] #[derive(Debug)] #[allow(non_camel_case_types)] @@ -18,7 +21,15 @@ pub enum SNIPS_RESULT { SNIPS_RESULT_KO = 1, } -/// An array of strings +/// A utility type to represent arrays of string +/// # Example +/// +/// ``` +/// use ffi_utils::{CReprOf, CStringArray}; +/// let pizza_names = vec!["Diavola".to_string(), "Margarita".to_string(), "Regina".to_string()]; +/// let c_pizza_names = CStringArray::c_repr_of(pizza_names).expect("could not convert !"); +/// +/// ``` #[repr(C)] #[derive(Debug)] pub struct CStringArray { @@ -78,6 +89,34 @@ impl CDrop for CStringArray { } } +/// A utility type to represent arrays of the parametrized type. +/// Note that the parametrized type should have a C-compatible representation. +/// +/// # Example +/// +/// ``` +/// use ffi_utils::{CReprOf, AsRust, CDrop, CArray}; +/// use libc::c_char; +/// +/// pub struct PizzaTopping { +/// pub ingredient: String, +/// } +/// +/// #[derive(CDrop, CReprOf, AsRust)] +/// #[target_type(PizzaTopping)] +/// pub struct CPizzaTopping { +/// pub ingredient: *const c_char +/// } +/// +/// let toppings = vec![ +/// PizzaTopping { ingredient: "Cheese".to_string() }, +/// PizzaTopping { ingredient: "Ham".to_string() } ]; +/// +/// let ctoppings = CArray::::c_repr_of(toppings); +/// +/// ``` +/// +/// #[repr(C)] pub struct CArray { data_ptr: *const T, @@ -88,7 +127,8 @@ impl, V> AsRust> for CArray { fn as_rust(&self) -> Result, Error> { let mut vec = Vec::with_capacity(self.size); if self.size > 0 { - let values = unsafe { std::slice::from_raw_parts_mut(self.data_ptr as *mut U, self.size) }; + let values = + unsafe { std::slice::from_raw_parts_mut(self.data_ptr as *mut U, self.size) }; for value in values { vec.push(value.as_rust()?); } @@ -100,23 +140,21 @@ impl, V> AsRust> for CArray { impl + CDrop, V> CReprOf> for CArray { fn c_repr_of(input: Vec) -> Result { let input_size = input.len(); - Ok( - Self { - data_ptr: if input_size > 0 { - Box::into_raw( - input - .into_iter() - .map(|item| U::c_repr_of(item)) - .collect::, Error>>() - .expect("Could not convert to C representation") - .into_boxed_slice() - ) as *const U - } else { - null() as *const U - }, - size: input_size, - } - ) + Ok(Self { + data_ptr: if input_size > 0 { + Box::into_raw( + input + .into_iter() + .map(|item| U::c_repr_of(item)) + .collect::, Error>>() + .expect("Could not convert to C representation") + .into_boxed_slice(), + ) as *const U + } else { + null() as *const U + }, + size: input_size, + }) } } @@ -136,4 +174,4 @@ impl Drop for CArray { fn drop(&mut self) { let _ = self.do_drop(); } -} \ No newline at end of file +}