Deriving ReprC for custom structs

Usage

use ::safer_ffi::prelude::*;

#[derive_ReprC] // <- `::safer_ffi`'s attribute
#[repr(C)]      // <- defined C layout is mandatory!
pub
struct Point {
    x: i32,
    y: i32,
}

#[ffi_export]
fn get_origin ()
  -> Point
{
    Point { x: 0, y: 0 }
}
Generated C header
typedef struct Point {
    int32_t x;
    int32_t y;
} Point_t;

Point_t get_origin (void);

Usage with Generic Structs

#[derive_ReprC] supports generic structs:

use ::safer_ffi::prelude::*;

/// The struct can be generic...
#[derive_ReprC]
#[repr(C)]
pub
struct Point<Coordinate> {
    x: Coordinate,
    y: Coordinate,
}

/// ... but its usage within an `#[ffi_export]`-ed function must
/// no longer be generic (it must have been instanced with a concrete type)
#[ffi_export]
fn get_origin ()
  -> Point<i32>
{
    Point { x: 0, y: 0 }
}
Generated C header

Each monomorphization leads to its own C definition:

  • Point<i32>

    typedef struct {
        int32_t x;
        int32_t y;
    } Point_int32_t;
    
  • Point<f64>

    typedef struct {
        double x;
        double y;
    } Point_double_t;
    

Requirements

  • All the fields must be ReprC or generic.

    • In the generic case, the struct is ReprC only when instanced with concrete ReprC types.
  • The struct must be non-empty (because ANSI C does not support empty structs)

Opaque types (forward declarations)

Sometimes you may be dealing with a complex Rust type and you don't want to go through the hassle of recusrively changing each field to make it ReprC.

In that case, the type can be defined as an opaque object w.r.t. the C API, which will make it usable by C but only through a layer of pointer indirection and function abstraction:

#[derive_ReprC]
#[repr(opaque)] // <-- instead of `#[repr(C)]`
pub
struct ComplicatedStruct {
    path: PathBuf,
    cb: Rc<dyn 'static + Fn(&'_ Path)>,
    x: i32,
}

Only braced struct definitions are currently supported. Opaque tuple structs and enums ought to supported soon.

Example
use ::std::{
    path::{Path, PathBuf},
    rc::Rc,
};

use ::safer_ffi::prelude::*;

#[derive_ReprC]
#[repr(opaque)]
pub
struct ComplicatedStruct {
    path: PathBuf,
    cb: Rc<dyn 'static + Fn(&'_ Path)>,
    x: i32,
}

#[ffi_export]
fn create ()
  -> repr_c::Box<ComplicatedStruct>
{
    Box::new(ComplicatedStruct {
        path: "/tmp".into(),
        cb: Rc::new(|path| println!("path = `{}`", path.to_string_lossy())),
        x: 42,
    }).into()
}

#[ffi_export]
fn call_and_get_x (it: &'_ ComplicatedStruct)
  -> i32
{
    (it.cb)(&it.path);
    it.x
}

#[ffi_export]
fn destroy (it: repr_c::Box<ComplicatedStruct>)
{
    drop(it)
}
Generated C header
/* Forward declaration */
typedef struct ComplicatedStruct ComplicatedStruct_t;

ComplicatedStruct_t * create (void);

int32_t call_and_get_x (
    ComplicatedStruct_t const * it);

void destroy (
    ComplicatedStruct_t * it);

Testing it from C
#include <assert.h>
#include <stdlib.h>

#include "dem_header.h"

int main (
    int argc,
    char const * const argv[])
{
    ComplicatedStruct_t * it = create();
    assert(call_and_get_x(it) == 42); // Prints 'path = `/tmp`'
    destroy(it);
    return EXIT_SUCCESS;
}

Going further

Transparent newtype wrapper
use ::safer_ffi::{prelude::*, ptr};

/// A `Box`-like owned pointer type, but which can be freed using `free()`.
#[derive_ReprC]
#[repr(transparent)]
pub struct Malloc<T>(ptr::NonNullOwned<T>);

impl<T> Malloc<T> {
    pub fn new(value: T) -> Option<Self> {
        /* Uses `posix_memalign()` to handle the allocation */
    }
}

This pattern allows you to define a new type with thus specific Rust semantics attached to it (e.g., specific constructor, destructor and methods) while hiding all that to the C side:

  • in the C world, Malloc<T> will be referred to in the same way that ptr::NonNullOwned<T> is, i.e., as a (non-nullable) *mut T.
Example
#[ffi_export]
fn new_int (x: i32)
  -> Option<Malloc<i32>>
{
    Malloc::new(x)
}

would then generate:

int32_t * new_int (
    int32_t x);