Generating Rust bindings with bindgen
In the previous exercise you saw how easy it is for manual bindings to go out of
sync, and the scary silent corruption that can cause. In this chapter we’ll
introduce a tool to avoid all of this called bindgen. It’s a
library you call in your Rust build script to automatically generate Rust
bindings to C code based in C header files. It runs libclang on a C header and
emits matching Rust declarations: extern blocks, #[repr(C)] structs, integer
constants. The header remains the single source of truth, with the Rust bindings
being generated on every build.
You call bindgen from your build script like so:
let bindings = bindgen::Builder::default()
.header("c_src/bm_legacy.h")
// tell Cargo to re-run when headers change
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.generate()
.expect("bindgen failed");
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings.write_to_file(out_path.join("bindings.rs")).unwrap();
This writes the generated file into the crate’s OUT_DIR (usually
target/<debug or release>/build/<crate name and hash>/out). Inspecting the
generated file, you will see something like this:
/* automatically generated by rust-bindgen 0.72.1 */
unsafe extern "C" {
pub fn bm_add(a: ::std::os::raw::c_int, b: ::std::os::raw::c_int) -> ::std::os::raw::c_int;
}
You can then pull in this generated file into your crate by using the include!
macro like so:
mod sys {
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
}
This is great, we’ve gained end-to-end type checking. A change in the C header
will not silently corrupt our Rust code. But remember these bindings are as
unsafe as the C code itself. It’s your responsibility to use them correctly.
You would typically wrap them in a safe, idiomatic API. This is the common
-sys crate pattern: a <crate>-sys crate holds the raw bindings, while the
<crate> crate on top exposes the safe abstractions.
Head to the exercise
Replace the hand-written extern block from the previous chapter with bindgen
in build.rs. Safe wrappers and tests stay identical.
Tip
cargo expand -p bm_bindgen shows what bindgen produced. Requires
cargo-expand.
Exercise
The exercise for this section is located in 01_intro/03_bindgen