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 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'sobject
must not be interpreted unless you know it's type0x0000000000000001
: A data array with this layout:typedef struct data_array_t;
0x0000000000000002
: An object array with this layout:typedef struct 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 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 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'sdealloc
function - If the
dealloc
field is null, the underlyingobject
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
andobject
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_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.