Exposing Rust to C with cheadergen
So far, we’ve always called C from Rust. Now we flip. Remember that our goal is to migrate C code to Rust. We’ll eventually need C to call into our new Rust code!
To make a Rust function callable from C we’ll need to first annotate it correctly:
use std::ffi::c_char;
#[unsafe(no_mangle)]
pub extern "C" fn bm_strlower(s: *mut c_char) {
// ...
}
Much like the FFI function declaration we saw in the first exercise, we instruct
rustc to use a C-compatible calling convention through extern "C" fn (without
it, Rust uses its own ABI that of course C knows nothing about).
#[unsafe(no_mangle)] keeps the symbol name verbatim in the compiled object.
Otherwise Rust would emit a so-called mangled symbol name, a pseudo-random
identifier that in regular Rust code prevents two equally named types or
functions from causing “duplicate symbol” linking errors.
In this case we do want the symbol name to be the same plain identifier we gave it, so the C code linking to us can actually find the function.
The second piece is the header file that allows the C compiler to understand the
API. You could write it by hand, but you’d be maintaining two sources of truth.
cheadergen (the inverse of bindgen) generates C
headers from Rust code.
cheadergen (created and maintained by fellow Mainmatter engineer Luca
Palmieri) will read your Rust code and automatically generate a C header for the
relevant FFI-visible symbols.
Install the CLI by following the instructions here https://github.com/LukeMathWalker/cheadergen/releases.
Then you can run the following command in your crate root (e.g. the current exercise crate):
cheadergen generate
and a header will appear:
/* Generated by cheadergen — do not edit */
void bm_strlower(char *s);
cheadergen comes with two ways to configure its output. The first is a
project-wide cheadergen.toml file. Here you can tweak the style of the
generated headers, include custom preamble text, and more. See the full
documentation here.
The second configuration option is the #[cheadergen::config(...)] attribute.
You can apply this attribute to any Rust type. It lets you configure per-type
options such as renaming fields, skipping exporting, forcing exporting, and
more. See the full documentation here.
A note on cbindgen
If you’ve been researching Rust FFI tooling, you undoubtedly came across
cbindgen, a very similar tool maintained by Mozilla, and you may
ask yourself: “Why did you just introduce me to this custom tool instead?”
Well, we wouldn’t have built a new tool if the existing one fit. cbindgen
dates back to 2017, when the Rust ecosystem looked very different. To read your
Rust and emit headers, it had to parse the source itself with a custom
syn-based parser. At the time, that was the only way, since the compiler
didn’t expose enough information.
But the technique has real limits. The custom parser doesn’t always agree with
rustc, it often needs manual steering and fixups, and when it hits something it
can’t handle it tends to silently skip generating a header rather than tell you
why. None of this is a knock on cbindgen: it’s what the constraints of the era
allowed. cheadergen exists because those constraints have since eased, letting
us build something with better diagnostics and fewer surprises.
Head to the exercise
You’ll port the bm_strlower function to Rust and call
cheadergen generate --lang c --output-dir c_test -p cheadergen to generate the
header automatically.
Exercise
The exercise for this section is located in 01_intro/04_cheadergen