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: