Comparison with cbindgen
cbindgen is the established tool for
generating C/C++ headers from Rust. cheadergen is a younger alternative,
built on different foundations.
This page explains where they differ and how to decide which one fits your project.
Why a a new tool?
cbindgen parses your Rust source files to infer the shape of the C API for
your project. It’s an approach with quite a few downsides:
- Limited module resolution and type-level analysis.
- No macro expansion without resorting to
cargo expandor ad-hoc code paths. - Visibility into other crates is shallow.
In other words, cbindgen can’t tap into the rich view that’s available to the
Rust compiler for a given crate. This limitation can create significant friction in
real world projects; enough friction to build an alternative, cheadergen!
cheadergen relies on the Rust compiler for compile-time reflection, via
rustdoc-json.
That gives it module resolution, type checking, accurate cross-crate
references, and macro expansion for free.
But it’d be dishonest to state that cheadergen is a clear-cut improvement
for all projects: there are some downsides and limitations you should be aware of
before choosing one or the other for your next project.
Behavioural differences
Annotation syntax
cbindgen uses doc-comment annotations:
#![allow(unused)]
fn main() {
/// cbindgen:rename-all=ScreamingSnakeCase
#[repr(C)]
pub enum Status { Ok, Error }
}
cheadergen uses proc-macro attributes that participate in normal Rust
compilation:
#![allow(unused)]
fn main() {
#[cheadergen::config(rename_all = "SCREAMING_SNAKE_CASE")]
#[repr(C)]
pub enum Status { Ok, Error }
}
cheadergen downsides
#[cfg] attributes
cbindgen translates #[cfg(...)] into #ifdef ... directives so a single
header works across platforms. cheadergen can’t do this (see
Limitations).
Constants in array lengths
cbindgen preserves named constants in array-length positions
(int32_t x[FOO]). cheadergen receives the array length as a resolved
literal value from rustdoc JSON, so it emits int32_t x[10] and loses the
constant’s name in that position only.
Doc generation
cheadergen must generate JSON documentation for all packages that appear
in the C API exposed by your Rust project. This implies a cargo doc invocation
that, in most cases, is going to be slower than parsing source code via syn
(the approach taken by cbindgen).
The slowdown is mitigated via a disk-based cache for third-party crates, but
it’s worth calling out as a downside compared to cbindgen.
Furthermore, this requires you have to a nightly toolchain installed,
at least on dev machines. This issue may go away in the future,
once rustdoc-json stabilizes.
When you’d pick cbindgen
- You need
#[cfg]-to-#ifdeftranslation (Firefox-style multi-platform headers). - You need C++-idiomatic output (
enum class, custom constructors,derive-ostream, etc.). - You need Cython output
- You can’t use a
nightlytoolchain for build-time tools.
When you’d pick cheadergen
- You’re on a Cargo workspace with multiple FFI-facing crates and want partitioned headers that share types coherently.
- You value type-checked, IDE-friendly annotations over doc-comment strings.
- Your platform set is narrow enough that the missing
#[cfg]handling isn’t a blocker.
If you’re using cbindgen on a project, and you want to migrate over,
check out our migration guide.