diff --git a/assets/c-friendly-ffi-design.md b/assets/c-friendly-ffi-design.md deleted file mode 100644 index 70d1644..0000000 --- a/assets/c-friendly-ffi-design.md +++ /dev/null @@ -1,81 +0,0 @@ -# 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 ``. C++ supports `bool` natively - and does not need any header file. -- In C, the integer types are provided by ``. In C++, they are provided - by ``. -- In C, the `uintptr_t` and `intptr_t` types are provided by ``. In C++, - they are provided by ``. -- 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. diff --git a/assets/c-friendly-ffi-design/allowed-data-types.md b/assets/c-friendly-ffi-design/allowed-data-types.md new file mode 100644 index 0000000..e716797 --- /dev/null +++ b/assets/c-friendly-ffi-design/allowed-data-types.md @@ -0,0 +1,50 @@ +# Allowed Data Types + +Under this section we discuss which data types are allowed to appear in an FFI +interface and how to convert Rust data types into these allowed types. The +correspondence between these types and their C/C++ counterparts is covered in +[C/C++ Headers](c-cpp-headers.md). + +## 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 Rust types are allowed to be passed directly across the FFI boundary: + +- `bool` +- `f32` +- `f64` +- `i8` +- `i16` +- `i32` +- `i64` +- `u8` +- `u16` +- `u32` +- `u64` +- `usize` +- `isize` + +### 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 + +The Rust `char` type only holds valid Unicode scalar values. 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: + +- 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. diff --git a/assets/c-friendly-ffi-design/c-cpp-headers.md b/assets/c-friendly-ffi-design/c-cpp-headers.md new file mode 100644 index 0000000..c6fabc9 --- /dev/null +++ b/assets/c-friendly-ffi-design/c-cpp-headers.md @@ -0,0 +1,72 @@ +# C/C++ Headers + +Under this section we discuss how to write the C/C++ header files that accompany the +Rust FFI library. The Rust-side counterpart of each topic is covered in +[FFI Function Signatures](ffi-function-signatures.md) and +[Allowed Data Types](allowed-data-types.md). + +## Function Declaration Style + +FFI functions must be exported under C names, that is, they must not go through the +C++ name mangling mechanism. In the generated C header file, you need to use the +`extern "C"` specifier, the `__cplusplus` macro and macro conditionals to import the +declarations correctly, for example: + +```c++ +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +CError WFStartup(void); + +// More FFI functions omitted... + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus +``` + +## Required Header Files + +The generated C/C++ header files must include the appropriate header files for the +types used in the declarations. + +- In C, the `bool` type is provided by ``. C++ supports `bool` natively + and does not need any header file. +- In C, the integer types are provided by ``. In C++, they are provided + by ``. +- In C, the `uintptr_t` and `intptr_t` types are provided by ``. In C++, + they are provided by ``. +- The floating-point types do not require any header file. + +## Type Correspondences + +The following table shows the correspondence between the Rust types that can cross +the FFI boundary directly (listed in [Allowed Data Types](allowed-data-types.md)) +and their C/C++ counterparts. 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` | + +## The char Problem + +For the Rust `char` type, the C/C++ header side uses `char32_t` as the parameter +type. Note that `char32_t` can hold any 32-bit unsigned integer, which is broader +than the set of valid Unicode scalar values carried by the Rust `char` type. The +Rust side is responsible for validating the incoming value; see +[The char Problem](allowed-data-types.md#the-char-problem) in +[Allowed Data Types](allowed-data-types.md). diff --git a/assets/c-friendly-ffi-design/ffi-function-signatures.md b/assets/c-friendly-ffi-design/ffi-function-signatures.md new file mode 100644 index 0000000..1c771e1 --- /dev/null +++ b/assets/c-friendly-ffi-design/ffi-function-signatures.md @@ -0,0 +1,94 @@ +# FFI Function Signatures + +Under this section we discuss the signature requirements of the exported FFI +functions. + +## C-style Name Export + +FFI functions must be exported under C names, that is, they must not go through the +C++ name mangling mechanism. + +In Rust, you need to decorate the function with the `#[unsafe(no_mangle)]` attribute +together with `extern "C"`, for example: + +```rust +#[unsafe(no_mangle)] +pub extern "C" fn WFStartup() -> CError { + // Function body omitted... +} + +// More FFI functions omitted... +``` + +## Export Name Style + +In this subsection, we discuss the naming style of exported functions. + +In this design, there are exactly two styles to choose from. We call them the +Windows style and the POSIX style. Either style can be chosen freely, but within a +single library the two styles must not be mixed. + +We recommend the following selection rule: if your library is Windows-only, choose +the Windows style; otherwise, choose the POSIX style. + +### Windows Style + +The Windows style format is `` for ordinary functions, and +`` for opaque struct functions. + +In the format, `` is the name of the opaque struct and `` is the name +of the function. Both of them must use PascalCase naming. + +For ``, it is a prefix identifier that is specific to this library. As we +all know, C has no namespace concept, and all functions are exported into the same +space. Therefore, avoiding name collisions between functions is an important task. +By prepending `` to the function name, name duplication can be avoided as +much as possible. For this reason, you need to choose `` carefully and try +to pick a prefix that is unlikely to collide. + +In the Windows style, there are two spellings for ``. One is the +all-uppercase style and the other is the style in which only the first letter is +uppercase and all remaining letters are lowercase. Choose according to your own +needs. In addition, the length of `` must not exceed 5 characters, and +it must not contain anything other than letters and digits (that is, underscores are +not allowed). + +The following examples show some legal and illegal Windows style function names: + +| Name | Verdict | Reason | +|------|---------|--------| +| `WFStartup` | Legal | An ordinary (non opaque struct) function. | +| `WFIconGetHandle` | Legal | An opaque struct function whose `` is `Icon`. | +| `WFWordHandleGetHandle` | Legal | An opaque struct function whose `` is `WordHandle`. | +| `FlGetMessage` | Legal | Uses a ``, which is `Fl`, that capitalizes only its first letter. | +| `MyRustGetMessage` | Illegal | ``, which is `MyRust`, does not keep all remaining letters lowercase. | +| `My_RustGetMessage` | Illegal | `` contains an underscore. | +| `IconGetHandle` | Illegal | Missing ``. | +| `QwertyuiopGetData` | Illegal | ``, which is `Qwertyuiop`, is too long. | +| `WFget_message` | Illegal | ``, which is `get_message`, is not PascalCase. | +| `WFicon_handleGetMessage` | Illegal | ``, which is `icon_handle`, is not PascalCase. | + +### POSIX Style + +The POSIX style format is `_` for ordinary functions, and +`__` for opaque struct functions. + +The ``, `` and `` components in the format have the same +meaning as in the Windows style, but the format is different. `` and `` +must use snake_case naming. `` must be all lowercase, and it must not contain +anything other than letters and digits (that is, underscores are not allowed). Its +length and selection requirements are the same as in the Windows style. + +The following examples show some legal and illegal POSIX style function names: + +| Name | Verdict | Reason | +|------|---------|--------| +| `wf_startup` | Legal | An ordinary (non opaque struct) function. | +| `wf_icon_get_handle` | Legal | An opaque struct function whose `` is `icon`. | +| `wf_word_handle_get_handle` | Legal | An opaque struct function whose `` is `word_handle`. | +| `Fl_get_message` | Illegal | ``, which is `Fl`, is not all lowercase. | +| `my_rust_get_message` | Illegal | ``, which is `my_rust`, contains an underscore. | +| `icon_get_handle` | Illegal | Missing ``. | +| `qwertyuiop_get_data` | Illegal | ``, which is `qwertyuiop`, is too long. | +| `wf_GetMessage` | Illegal | ``, which is `GetMessage`, is not snake_case. | +| `wf_Icon_get_message` | Illegal | ``, which is `Icon`, is not snake_case. | diff --git a/assets/c-friendly-ffi-design/index.md b/assets/c-friendly-ffi-design/index.md new file mode 100644 index 0000000..cfe6cb1 --- /dev/null +++ b/assets/c-friendly-ffi-design/index.md @@ -0,0 +1,13 @@ +# 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. + +## Contents + +- [FFI Function Signatures](ffi-function-signatures.md) +- [Allowed Data Types](allowed-data-types.md) +- [C/C++ Headers](c-cpp-headers.md)