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
