doc: writing design doc
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user