A demonstration project showing how to write a library in Rust,
expose its API to C/C++ via FFI, and build everything with a unified CMake process.
.
├─ Cargo.toml # Cargo manifest of the Rust library
├─ cbindgen.toml # Config for generating C headers
├─ CMakeLists.txt # Unified build (Rust + C++)
├─ src/
│ ├─ core.rs # Pure Rust implementation (Counter)
│ ├─ ffi.rs # FFI wrappers (extern "C")
│ └─ lib.rs # Entry point, re-exports
├─ cpp/
│ └─ main.cpp # C++ usage example
└─ build/ # CMake build directory
Build the project using CMake:
mkdir -p build
cd build
cmake -DCMAKE_BUILD_TYPE=Debug .. # or Release
makeResults:
- Rust library (
.so,.dll,.dylib) is built intotarget/{debug,release}/ - C header
rust_ffi_demo.his generated intobuild/ - Example binary
cpp_exampleis built and linked against the Rust library
The header is generated automatically using cbindgen.
cbindgen.toml is configured to export only:
CounterHandle(opaque handle)RustFfiDemoStatus(error/status enum)
The internal Rust structure Counter is not exposed to C API.
Example (cpp/main.cpp):
#include "rust_ffi_demo.h"
#include <iostream>
int main() {
CounterHandle* counter = nullptr;
rust_ffi_demo_counter_new(10, &counter);
rust_ffi_demo_counter_increment(counter, 5);
long long value = 0;
rust_ffi_demo_counter_value(counter, &value);
std::cout << "Counter value = " << value << std::endl;
rust_ffi_demo_counter_free(counter);
}The library supports per-counter callbacks: each Counter can be associated with a callback function that is automatically fired when the counter value changes.
let mut c1 = Counter::new(10);
c1.set_label(Some("alpha".into()));
c1.set_callback(Some(Box::new(|val| {
println!("[Rust callback] Counter changed to {}", val);
})));
println!("c1 before increment: {:?}", c1);
c1.increment(5); // triggers callback
println!("c1 after increment: {:?}", c1);
let c2 = c1.clone(); // cloned without callback
println!("c2 clone: {:?}", c2);Output:
c1 before increment: Counter { value: 10, label: Some("alpha"), .. }
[Rust callback] Counter changed to 15
c1 after increment: Counter { value: 15, label: Some("alpha"), .. }
c2 clone: Counter { value: 15, label: Some("alpha"), .. }
Extend main.cpp with:
// Our callback function
void on_value_changed(long long value) {
std::cout << "[C++] Callback fired! Counter changed, new value = " << value << std::endl;
}
...
// Register callback
rust_ffi_demo_counter_set_callback(counter, on_value_changed);
// Trigger events
rust_ffi_demo_counter_increment(counter, 7); // callback fires
rust_ffi_demo_counter_reset(counter); // callback firesExpected output:
Counter value = 15
Registering callback...
Incrementing by 7 (should trigger callback)...
[C++] Callback fired! Counter changed, new value = 22
Resetting counter (should trigger callback)...
[C++] Callback fired! Counter changed, new value = 0
Recommended extensions:
- rust-analyzer
- CodeLLDB
Example .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug cpp_example + Rust",
"type": "lldb",
"request": "launch",
"program": "${workspaceFolder}/build/cpp_example",
"cwd": "${workspaceFolder}/build",
"env": {
"LD_LIBRARY_PATH": "${workspaceFolder}/target/debug"
},
"sourceLanguages": ["rust", "cpp"]
}
]
}You can set breakpoints both in cpp/main.cpp and in Rust sources (src/ffi.rs).
MIT