Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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