This document discusses interfacing Rust and C/C++ code using FFI. It describes using CBindgen to generate C/C++ headers from Rust to expose functions and types to C/C++. Similarly, Bindgen can generate Rust bindings from C/C++ headers. Error handling across the FFI boundary using Result instead of exceptions is also covered. Finally, issues like different ABIs, calling conventions and allocators between languages are discussed.
5. How crates work
● Package manager concept (cargo)
● A crate is the way to package a module
● Can generate executable and libraries
● Can contain subfolders
● Handles the dependencies using cargo
● Compile-time scripting
ROOT
src
lib.rs # entry point for libs
main.rs # entry point for exe
Cargo.toml
Build.rs - run at compile-time
6. CBindgen
● Generates C or C++ headers that wraps the Rust code
● Functions need to be annotated with #[no_mangle]
● Types need to be annotated with #[repr(C)]
● Does not handle all cases
● Can be run standalone or at compile time
8. Rust -> C
C
int call_me(int i);
Rust
extern “C” {
fn call_me(i:i32) -> i32;
}
fn main() {
let ret = call_me(42);
}
9. Bindgen
● Parses the C headers and generates Rust wrappers.
● Run at compile time or as standalone application.
● Convention is to make a new crate with the suffix -sys for each lib. Ex. openssl-sys
● Recommended to create a C header in the crate as the starting point.
● Can handle C++ to some extent.
● Define macros are converted in constants
● Function macros are ignored
● Templates are ignored
11. Error handling
● Safe Rust does not have NULL, instead uses Optional<T>.
● Rust has pointers, but operation needs to be done in unsafe code.
● For error handling Rust uses Result<T,E>, where E is the error type.
● Rust has something similar to exceptions called panic. Are used only in
specific cases for fatal errors.
● C++ uses exceptions and return codes.
● Panics and exceptions are not compatible.
● Exceptions MUST NOT cross the FFI boundary
● Use a common logging system. Most rust logging crates provide a way to
write custom adaptors.
12. Error handling - Exceptions to Result
1. Parse C++ headers using `clang -Xclang -fsyntax-only –ast-dump=json`
2. For each possible return type create a variant that contains the return type
and the exception in C
3. In Rust for each variant write conversion functions to Result<T,E>
4. For each function generate a wrapper that catches the exception and
converts it to the variant in C
5. Generate Rust bindings only for the wrappers
13. Error handling - Result to Exceptions
1. Parse Rust code using `syn` crate
2. For each possible Result used generate in Rust a variant that can be created
from a Result
3. For each function generate in Rust a wrapper that calls the functions and
converts the result to the variant.
4. For each wrapper generate in C++ another wrapper that converts the variant
to an exception
14. Callbacks
● Hard to wrap.
● Easy to throw exceptions in the middle of that code.
● Either write them with care or create a wrapper that handles all the error
handling.
● Thread safety
● Use the ‘user data’ pattern when posibile.
15. Issues
● Different C/C++ compilers use different underlying types for common types.
● char might be signed or unsigned, 1 byte or 4 bytes.
● Use fixed sized type when possible.
● Calling convention might be different.
● Make sure the generated code uses the same compiler flags as the normal
one
● Rust might use different allocator then C code, so freeing needs to be
handled in the same code base.
● Rust strings are UTF-8 and are not NULL terminated. Conversions need to be
done using the CString/&cstr classes.
16. Good news
● Gdb and rr can works with both languages at the same time.
● Backtraces will make sense.
● ‘Go-to definition’ feature will work in your IDE.