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
booltype is provided by<stddef.h>. C++ supportsboolnatively 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_tandintptr_ttypes 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
u32as the parameter type. - For input parameters, use
char::try_fromto perform a fallible conversion that validates the incoming character. - For output parameters, use
u32::fromfor the conversion.
- The function signature uses
- On the C/C++ header side:
- The function signature uses
char32_tas the parameter type.
- The function signature uses