Files
sarasacw-omrf/assets/c-friendly-ffi-design.md
T
2026-08-06 20:22:17 +08:00

2.9 KiB

C-friendly FFI Design

This document is a guide for authoring Rust libraries that expose a C-friendly FFI and distributing them as ordinary CMake/pkg-config packages. It covers how to design C-friendly FFI signatures, how to pass primitive types, enums, strings and Rust-specific constructs such as Option and Result across the boundary, and how to write the accompanying C/C++ header files.

Allowed Data Types

Under this section we discuss which data types are allowed to appear in an FFI interface, how to pass them across the boundary, and how to convert Rust data types into these allowed types.

Primitive Type Conversion

This subsection covers the conversion of primitive types. The rules described here apply to both parameter and return value positions.

Types Allowed to Cross the Boundary Directly

The following types are allowed to be passed directly across the FFI boundary. The left column lists the Rust type and the right column lists the corresponding C/C++ type.

Rust C/C++
bool bool
f32 float
f64 double
i8 int8_t
i16 int16_t
i32 int32_t
i64 int64_t
u8 uint8_t
u16 uint16_t
u32 uint32_t
u64 uint64_t
usize uintptr_t
isize intptr_t

Required Header Files in the Generated C/C++ Headers

The generated C/C++ header files must include the appropriate header files for the types listed above.

  • In C, the bool type is provided by <stddef.h>. C++ supports bool natively and does not need any header file.
  • In C, the integer types are provided by <stdint.h>. In C++, they are provided by <cstdint>.
  • In C, the uintptr_t and intptr_t types are provided by <stdint.h>. In C++, they are provided by <cstdint>.
  • The floating-point types do not require any header file.

The u128 and i128 Problem

The u128 and i128 types have very poor support in the C/C++ standard libraries. Therefore they cannot be passed directly across the FFI boundary.

To transfer data of these types, refer to the later section about passing opaque structs.

The char Problem

In C/C++, the type closest to the Rust char type is char32_t. However, the Rust char type only holds valid Unicode scalar values, which conflicts with char32_t being any 32-bit unsigned integer.

In addition, the Rust official documentation explicitly states that the char type does not have FFI safety, so it cannot be used as a value passed across the FFI boundary.

The solution is to use the Rust u32 type for passing. Under this scheme:

  • On the Rust side:
    • The function signature uses u32 as the parameter type.
    • For input parameters, use char::try_from to perform a fallible conversion that validates the incoming character.
    • For output parameters, use u32::from for the conversion.
  • On the C/C++ header side:
    • The function signature uses char32_t as the parameter type.