Use when a Rust crate needs to be called from Python, Node.js, Swift, or Kotlin without a separate FFI codegen step.
Rust FFI Binding Generation
Overview
cbindgen reads Rust extern "C" blocks and emits idiomatic C headers.
Pair with language-specific binders: cffi (Python), node-ffi-napi (Node),
a .swift wrapper (Swift), or JNA (Kotlin/Java).
Procedure
cargo add --build cbindgenand add abuild.rsthat callscbindgen::generate().write_to_file("mycrate.h").- Annotate every
#[no_mangle] extern "C"function with#[repr(C)]and use only POD-compatible types (ints, pointers, slices as*const/*mut). - For strings, pass
*const c_char(null-terminated) or(*const u8, usize)byte-slice pairs to avoid allocator coupling. - Build:
cargo build --release. Distribute the.so/.dylib/.dllalongside the generated header. - Language binders:
- Python:
ffi.cdef(open("mycrate.h").read()); lib = ffi.dlopen("libmycrate.so") - Node: define via
ffi-napiusing the same C signatures. - Swift:
import Glibc/Darwinand call through the.so.
- Python:
- Never cross an allocator boundary — Rust must allocate+deallocate (use a
freefunction exported the same way), or copy into a caller-owned buffer.
Pitfalls
- Panics across the FFI boundary are UB; wrap entry points in
std::panic::catch_unwinding. String/Vecare NOT C-compatible — leak or segfault.- Debug vs release ABIs differ; always ship release builds.
Verification
-
cbindgen --config cbindgen.toml --crate mycrate --output mycrate.hexits 0. -
strings libmycrate.so | grep <exported_symbol>shows the symbol. - Python:
python3 -c "import mymod; print(mymod.add(2,3))"prints 5.