Skip to content

Commit

Permalink
Merge pull request #16 from snipsco/documentation
Browse files Browse the repository at this point in the history
Documentation
  • Loading branch information
anthonyray authored Mar 16, 2020
2 parents 15d0ec9 + 6a5f0fd commit 768c234
Show file tree
Hide file tree
Showing 5 changed files with 262 additions and 22 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
2 changes: 2 additions & 0 deletions ffi-utils-derive/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! This crate provides ffi_convert derive macros for CReprOf, AsRust and CDrop traits.
extern crate proc_macro;

mod asrust;
Expand Down
29 changes: 29 additions & 0 deletions ffi-utils/src/conversions.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
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) => {
$crate::convert_to_c_string_result!($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) => {
Expand All @@ -17,6 +24,9 @@ macro_rules! convert_to_c_string_result {
};
}

/// A macro to convert a `Vec<String>` 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) => {{
Expand All @@ -25,6 +35,9 @@ macro_rules! convert_to_c_string_array {
}};
}

/// A macro to convert a `Vec<String>` 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) => {
Expand All @@ -36,6 +49,8 @@ macro_rules! convert_to_nullable_c_string_array {
};
}

/// A macro to convert an `Option<String>` 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) => {
Expand All @@ -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) => {{
Expand All @@ -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) => {
Expand All @@ -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) => {{
Expand All @@ -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) => {
Expand All @@ -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) => {{
Expand All @@ -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) => {
Expand All @@ -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) => {{
Expand All @@ -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) => {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -210,15 +234,18 @@ pub trait RawPointerConverter<T>: Sized {
}
}

/// Trait to create borrowed references to type T, from a raw pointer to a T
pub trait RawBorrow<T> {
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<T> {
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<T> RawPointerConverter<T> for T {
fn into_raw_pointer(self) -> *const T {
Box::into_raw(Box::new(self)) as _
Expand All @@ -233,6 +260,7 @@ impl<T> RawPointerConverter<T> for T {
}
}

/// Trait that allows obtaining a borrowed reference to a type T from a raw pointer to T
impl<T> RawBorrow<T> for T {
unsafe fn raw_borrow<'a>(input: *const T) -> Result<&'a Self, Error> {
input
Expand All @@ -241,6 +269,7 @@ impl<T> RawBorrow<T> for T {
}
}

/// Trait that allows obtaining a mutable borrowed reference to a type T from a raw pointer to T
impl<T> RawBorrowMut<T> for T {
unsafe fn raw_borrow_mut<'a>(input: *mut T) -> Result<&'a mut Self, Error> {
input
Expand Down
169 changes: 169 additions & 0 deletions ffi-utils/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<Topping>,
//! pub base: Option<Sauce>,
//! 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<CTopping>,
//! 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<Topping>,
//! # 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<CTopping>,
//! 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
//!
//! <table>
//! <thead>
//! <tr>
//! <th>C type</th>
//! <th>Rust type</th>
//! <th>C-compatible Rust type</th>
//! </tr>
//! </thead>
//! <tbody>
//! <tr>
//! <td><code>const char*</code></td>
//! <td><code>String</code></td>
//! <td><code>*const libc::c_char</code></td>
//! </tr>
//! <tr>
//! <td><code>const T</code></td>
//! <td><code>Option<T></code></td>
//! <td><code>*const T</code> (with <code>#[nullable]</code> field annotation)</td>
//! </tr>
//! <tr>
//! <td><code>CArrayT</code></td>
//! <td><code>Vec<T></code></td>
//! <td><code>CArray<T></code></td>
//! </tr>
//! </tbody>
//! </table>
//!
//! ```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<T>: Sized + CDrop {
//! fn c_repr_of(input: T) -> Result<Self, Error>;
//! }
//! ```
//! 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<T> {
//! fn as_rust(&self) -> Result<T, Error>;
//! }
//! ```
//! 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;
Expand Down
Loading

0 comments on commit 768c234

Please sign in to comment.