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

Generate headers for a Cargo workspace

cheadergen was designed for Cargo workspaces with multiple FFI-facing crates that share types. In partitioned mode (the default) the right shape falls out automatically: each crate that contributes types gets its own header, and consuming crates #include it.

This guide shows the common patterns. For the mental model of which crates participate in a run and why, see Target packages.

Select multiple target packages

Pass --package multiple times to generate headers for several workspace members in one invocation:

cheadergen generate \
    --output-dir include \
    --package alpha \
    --package beta

Each --package argument names a workspace member that should produce a “target” header (one with extern "C" function declarations, not just types).

Alternatively, omit --package and use --input-dir to target every library member under one or more workspace directories:

cheadergen generate \
    --output-dir include \
    --input-dir crates/alpha \
    --input-dir crates/beta

Pass --input-dir multiple times to combine several directories. Each path must live inside the workspace rooted at the current directory.

Omitting both --package and --input-dir falls back to a Cargo-style default: if the current directory sits inside a workspace member it targets just that member, otherwise it targets the workspace’s default-members (every member, for a virtual workspace). See Target packages for the full rule.

What gets generated

Given two targets alpha and beta that both use a Config type from a workspace dependency core:

include/
├── alpha.h     # target — extern "C" functions and types unique to alpha
├── beta.h      # target — extern "C" functions and types unique to beta
└── core.h      # types-only — Config and other shared types
  • Both alpha.h and beta.h start with #include "core.h".
  • Config is defined exactly once, in core.h.
  • core.h has no extern "C" block — core isn’t a target, just a type contributor.

A non-target dependency only gets a header if at least one target actually uses its types. External crates (from crates.io) follow the same rules.

Customising header filenames

By default a crate named my-crate produces my_crate.h (hyphens become underscores). Override the base name per package with header_name:

# cheadergen.toml
[package."core"]
header_name = "myproject_core"

Now alpha.h and beta.h will #include "myproject_core.h" and the file on disk is renamed to match.

If a dependency name is ambiguous (multiple versions present), disambiguate with name@version:

[package."internal-state@1"]
header_name = "state_v1"

Pruning stale output

Add --prune-orphans to clean up any *.h files in the output directory that this run didn’t write. Useful when you remove types or packages:

cheadergen generate \
    --output-dir include \
    --prune-orphans \
    -p alpha \
    -p beta

Without --prune-orphans, removing a crate from the workspace leaves its header behind as silent stale output.

--prune-orphans only acts on top-level files matching the language extension (*.h for C); it never touches subdirectories or unrelated files.

Skipping empty headers

Some non-target crates contribute no concrete C-visible types, only generic definitions that get monomorphized into their consumers’ headers. By default cheadergen still writes an (empty) header file for those crates. Pass --skip-empty to suppress the empty files:

cheadergen generate \
    --output-dir include \
    --skip-empty \
    --prune-orphans

--skip-empty is partitioned-only (it’s rejected with --bundle). Combining it with --prune-orphans ensures empty-by-deletion headers also get cleaned up.

See also