Extern C Gets Old, Fast

It's not really a big deal for "hello world" - but once you start linking with larger C libraries, you'll really start to hate writing extern "C" { ... } blocks with all the function signatures.

Let's pretend that Hello World is a real library, and make add hello.h (next to hello.c in the src directory):

#ifndef HELLO_H
#define HELLO_H

void say_hello(); /* Function prototype for say_hello() */

#endif

Yes, I know that #pragma once is a great way to start fights in the C++ community.

We'll use a tool named bindgen to read the header file and build the Rust for us. Let's add bindgen to our build dependencies:

[workspace.dependencies]
cc = "1"
bindgen = "0"

Bindgen is proof that some projects never get to version 1. It's been 0.x since 2014!

Now we can update build.rs to generate the bindings for us:

use std::env;
use std::path::PathBuf;

fn main() {
    // Read the header
    let bindings = bindgen::Builder::default()
        .header("src/hello.h")
        .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
        .generate()
        .expect("Unable to generate bindings");

    // Emit the bindings
    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
    bindings
        .write_to_file(out_path.join("hello.rs"))
        .expect("Couldn't write bindings!");

    // Build the C code
    cc::Build::new()
        .file("src/hello.c")
        .compile("hello");
}

And update main.rs to use the generated bindings. Replace the entire extern "C" block with:

#![allow(unused)]
fn main() {
include!(concat!(env!("OUT_DIR"), "/hello.rs"));
}

Now, on every compilation the hello.h will be turned into Rust (which is really handy if the C might change) and bindings generated.

Nestled somewhere in your target directory, you'll find a hello.rs file that looks like this:

#![allow(unused)]
fn main() {
/* automatically generated by rust-bindgen 0.70.1 */

extern "C" {
    pub fn say_hello();
}
}

Stuck?

If it didn't compile, you might need to install clang and llvm on your system. Go to: https://rust-lang.github.io/rust-bindgen/requirements.html for a guide.

There's also a Dockerfile available:

cd workshops/
docker build -t ex02 -f Dockerfile.ex02 .
docker run -it ex02