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: