The Simplest Rust C++ FFI Example
Tags:
Bottom Line: Practicing FFI and bindgen since I know so little about C++.
One of the many things to love about Rust is that it can integrate with existing codebases and has reasonably good tooling to help you get started. I still find it difficulty as I have no background in C or C++, so I thought a toy example might help me wrap my head around things.
First, clone the repo that has the source code for this post:
$ git clone https://github.com/n8henrie/rust-ffi-practice.git
Prove that the C++ source compiles and runs:
$ cd rust-ffi-practice/vendor/cpplib
$ clang++ *.cpp
$ ./a.out
before
42
and after
NB: I’m on MacOS; if you’re on Linux and don’t have clang, you should be able
to use g++ instead of clang++.
Afterward proving to yourself that it works, go back to the project root and
run cargo test.
Tests should passing, showing that you can call barfunc() from Rust even
though it was defined in vendor/cpplib/bar.cpp.
For this example, wrapper.h is a header file including the types for which
you’d like bindgen to generate Rust bindings.
If you leave the default #include "bar.h", it creates bindings for everything
defined in that file. (NB: It can only find that file due to providing the
include path with .clang_arg("-Ivendor/cpplib")). You can see what these
bindings look like by looking at the resulting bindings.rs file, which
resides a few subfolders deep in the target/ folder, and which we are
including in src/lib.rs at compile time. Depending on your shell, you might
be able to check it out like so:
$ ls target/**/bindings.rs
target/debug/build/practice-815814b3446f2c5b/out/bindings.rs
$ cat target/**/bindings.rs
/* automatically generated by rust-bindgen 0.59.1 */
extern "C" {
#[link_name = "\u{1}__Z7barfuncv"]
pub fn barfunc() -> ::std::os::raw::c_int;
}
extern "C" {
#[link_name = "\u{1}__Z6unusedv"]
pub fn unused() -> ::std::os::raw::c_int;
}
You’ll notice that a binding for unused() (from vendor/cpplib/bar.cpp) was
generated, even though it is not used in src/lib.rs (or anywhere for that
matter). That’s because we included bar.h in wrapper.h, so it generates a
binding for everything defined in bar.h.
If that were problematic or otherwise undesirable, there are a couple options
to prune the types that have bindings generated. For one, you can uncomment
.allowlist_function("barfunc") in build.rs, the rerun cargo test. If you
look at bindings.rs now, you’ll see that unused is no longer there.
Alternatively, you can comment out #include "bar.h" in wrapper.h and
uncomment int barfunc(); to have it only generate bindings for that specific
function.
I hope this is helpful for some of the other rust beginners out there; Rust FFI is a deep rabbit hole.
Some recommended reading: