C++ and Rust Interop: FFI, Memory Safety at the Boundary [#44-2]
이 글의 핵심
Why FFI is still unsafe: explicit contracts for pointers, lifetimes, and threading. Use extern C wrappers, document who frees, and automate bindings.
Introduction: “Do we rewrite everything in Rust?”
Rust is strong for memory safety and concurrency, but real systems often keep C++ and add Rust modules (or vice versa) via C ABI (FFI). Templates, exceptions, and C++ classes are not stable across compilers—expose C-style functions and plain structs only.
Topics: why interop exists, C ABI design, bindgen / cxx, common link and ABI errors, ownership at boundaries, incremental adoption.
Scenarios
- Reuse a validated C++ crypto or physics lib from Rust.
- Ship a Rust parser/core in a C++ GUI host.
- Ownership bugs: buffer freed on one side while the other still references it.
- No stable ABI for
std::vectoror exceptions across Rust↔C++—wrap with opaque pointers + length.
C ABI as the contract
extern "C"on C++ exports;#[no_mangle] extern "C"on Rust.- Plain POD layouts; fixed repr(C) in Rust.
- Document: who allocates, who frees, thread constraints.
sequenceDiagram
participant CPP as C++ app
participant ABI as C wrapper
participant RS as Rust lib
CPP->>ABI: rust_add(3,5)
ABI->>RS: exported C fn
RS-->>ABI: result
ABI-->>CPP: result
Tools
- bindgen: Rust from C headers.
- cxx: typed bridge between Rust and C++ with less manual unsafe (still review).
Build integration
- CMake + Cargo coordination: consistent PIC, stdlib, sanitizers.
- Link order and rpath issues are common—encode in CMake targets.
Memory safety discussion
Rust safety stops at unsafe and FFI. C++ side must uphold invariants Rust assumes. Treat FFI as its own mini specification with tests and fuzzing.
Related posts
- Rust vs C++ memory
- PIMPL / ABI
Summary
FFI is powerful but unsafe by default at the seam. Minimize surface area, automate bindings, and document ownership—then migrate incrementally with tests.