liveBook · Manning

Rust bindgen

As explained in Refactoring to Rust

bindgen is a code-generation tool that creates language bindings from C and C++ declarations for use from Rust.

Calling an existing C API from Rust requires Rust declarations that match its types and functions. bindgen generates those declarations from C or C++ headers, avoiding the need to write them all by hand. For an NGINX module, a build script runs bindgen before the Rust module is compiled. section 4.3.1 section 4.3.2

A build-script setup has four parts:

  • Add bindgen under Cargo’s build-dependencies, not ordinary dependencies: it generates code during the build but is not needed in the finished binary. section 4.3.2
  • Create a wrapper header that includes the C headers Rust needs. For the NGINX example, it includes ngx_config.h, ngx_core.h, and ngx_http.h. section 4.3.2
  • In build.rs, give bindgen the wrapper header and the include paths needed to find its dependencies; generate the bindings and write them to a file. section 4.3.2
  • Write the file into Cargo’s OUT_DIR and insert it into Rust source with include!(concat!(env!("OUT_DIR"), "/nginx.rs")). Reserve include! here for generated code, not ordinary Rust modules. section 4.3.1 section 4.3.2
Listing 4.5 Build script creating NGINX bindings for Rust
fn main() {
  let nginx_dir = "nginx-1.19.3";

  let bindings = bindgen::builder()
    .header("wrapper.h")  #1
    .clang_args(vec![
      format!("-I{}/src/core", nginx_dir), #2
      format!("-I{}/src/event", nginx_dir),  #2
      format!("-I{}/src/event/modules", nginx_dir),  #2
      format!("-I{}/src/os/unix", nginx_dir),  #2
      format!("-I{}/objs", nginx_dir), #2
      format!("-I{}/src/http", nginx_dir),  #2
      format!("-I{}/src/http/v2", nginx_dir),  #2
      format!("-I{}/src/http/modules", nginx_dir),  #2
    ]) #2
    .generate() #2
    .unwrap(); #2

  bindings
    .write_to_file("nginx.rs")  #3
    .unwrap();
}

The build.rs listing shows the header, NGINX include paths, generate call, and initial output file together. section 4.3.2

A broad wrapper header can produce far more bindings than the module needs, including C standard-library declarations. Restricting generated types, functions, and variables to the ngx_ prefix keeps that API surface smaller and avoids unnecessary compile-time work. section 4.3.2

Listing 4.6 bindgen build script only accepting ngx_ prefixed items
fn main() {
  let nginx_dir = "nginx-1.19.3";

  let bindings = bindgen::builder()
    .header("wrapper.h")
    .whitelist_type("ngx_.*")  #1
    .whitelist_function("ngx_.*")  #1
    .whitelist_var("ngx_.*") #1
    .clang_args(vec![ #1
      format!("-I{}/src/core", nginx_dir), #1
      format!("-I{}/src/event", nginx_dir),
      format!("-I{}/src/event/modules", nginx_dir),
      format!("-I{}/src/os/unix", nginx_dir),
      format!("-I{}/objs", nginx_dir),
      format!("-I{}/src/http", nginx_dir),
      format!("-I{}/src/http/v2", nginx_dir),
      format!("-I{}/src/http/modules", nginx_dir),
    ])
    .generate()
    .unwrap();

  bindings
    .write_to_file("nginx.rs")
    .unwrap();
}

Compare this filtered build script with the initial version: it adds whitelist rules for ngx_-prefixed types, functions, and variables. section 4.3.2

From declarations to an HTTP handler

Once nginx.rs is included, a Rust extern "C" handler can use generated names such as ngx_http_request_t and ngx_int_t. Generated bitfield setters also let response code call set_last_buf, set_last_in_chain, and set_memory on an NGINX buffer; Rust does not natively support those C bitfields. The bindings supply declarations, but the response code still checks pointers and copies its body into NGINX-owned memory rather than leaving NGINX with a pointer to a Rust string that may go out of scope. section 4.3.2 section 4.6

How Refactoring to Rust covers it

How deep it goes
Works through header selection, Cargo build configuration, generation, filtering, and inclusion; leaves cross-language memory handling to the handler code.
Examples
Uses an NGINX wrapper header, two build scripts, an extern "C" handler, and generated buffer-bitfield setters.
What it assumes
Assumes access to the target C headers and their include paths while introducing build scripts before applying bindgen.
What you can do after
Apply it via bindgen to generate and narrow NGINX C bindings for use in a Rust module.
How it connects
Connects C headers and foreign-function interfaces with Cargo build scripts, generated Rust code, and C bitfields.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Refactoring to Rust. The section links point to the parts of the book it draws on.