c_bridge 0.2.0

Some data structures and abstractions to create clean Rust<->C-FFI interfaces
Documentation

docs.rs License BSD-2-Clause License MIT crates.io Download numbers Travis CI AppVeyor CI dependency status

About c_bridge

This crate provides some data structures and abstractions to create clean Rust <-> C FFI interfaces

The FFI Object

This is an in-depth overview over the FFI object and the semantics behind it:

Concept

Each element is passed in an FFI object struct which looks like this and provides the following properties:

  • Layout:
    typedef struct {
        uint64_t type;
        void(*dealloc)(ffi_object_t*);
        void* payload;
    } ffi_object_t;
    
  • Typing: each FFI object provides information about the payload object's type
  • Ownership: each FFI object provides information about whether the the ownership is tied to the FFI object or if the object is just a reference to a payload object owned by someone else
  • Optionality: each FFI object may or may not contain a payload object

Typing

Each FFI object contains a uint64_t field with a type ID which specifies the type of the payload. The ID range is split into two segments:

  • [0x0000000000000000, 0x8000000000000000): A range reserved for predefined types
  • [0x8000000000000000, 0xffffffffffffffff]: A range reserved for implementation defined types

Predefined Types

  • 0x0000000000000000: An opaque type. This type is special because it indicates that the FFI object deliberately carries no type information and that it's object must not be interpreted unless you know it's type
  • 0x0000000000000001: A data array with this layout:
    typedef struct {
        uint8_t* data;
        size_t len;
    } data_array_t;
    
  • 0x0000000000000002: An object array with this layout:
    typedef struct {
        ffi_object_t* objects;
        size_t len;
    } object_array_t;
    
  • 0x0000000000000010: A Rust box containing an owned Rust object (Box<dyn Any + 'static>)
Data Array

The data array is a simple struct which contains a pointer to some data and a length field. The pointer must never be reallocated and must be deallocated by the FFI object's dealloc-function only.

Layout:

typedef struct {
    uint8_t* data;
    size_t len;
} data_array_t;
Object Array

The object array is a simple struct which contains a pointer to some some object structs and a length field. The pointer must never be reallocated and must be deallocated by the FFI object's dealloc-function only.

Layout:

typedef struct {
    ffi_object_t* objects;
    size_t len;
} object_array_t;
Rust Object

The Rust object is a pointer to a Box<dyn Any + 'static> which may typesafely contain an arbitrary Rust object.

Ownership

Since in the C world memory management happens manually, attention must be payed to questions like "Who owns the object" and "How do I release this object", which can be especially tedious if we need to cross FFI boundaries. To reduce this problem, we add explicit ownership information to each FFI object using the dealloc field:

  • If the dealloc field is non-null, this means that the FFI object is owned by itself and must be released by passing it to it's dealloc function
  • If the dealloc field is null, the underlying object is managed by someone else

If the FFI object is owned, some care must be taken to avoid problems like double-free or use-after-free:

  • Don't copy an owned FFI object
  • If you need to copy an FFI object, ensure that the shorter living one is unowned
  • If you move the payload object out of the FFI object, set the dealloc and object fields to null

Optionality

Each FFI object has an optional payload object. This is necessary to be able to move something out of the object (see Ownership for more information).

Optionality is represented in the most simple way possible:

  • An existing payload object is represented as a non-null pointer to the object
  • An empty FFI object is represented by a null pointer as payload object

The FFI Result Type

Together with the FFI object, we introduce another top-level type, the ffi_result_t:

typedef struct {
    ffi_object_t ok;
    ffi_object_t err;
} ffi_result_t;

This result type is similar to Rust's result and it's purpose is the same: The ability to return either a good result or some error information without error-pointer-arguments and other workarounds.

OK-Variant

To construct an ok-variant, set the ok-field to your result and set the err-field to an emtpy FFI object.

Note: if the err-field contains an empty FFI object, the FFI result must always be treated as ok - even if the ok-field contains an empty FFI object (this is to be able to mimic Rust's Result<(), E>).

Error-Variant

To construct an error-variant, set the err-field to a non-emtpy FFI object which contains your error and set the ok-field to an empty FFI object.