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

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