# cargo-c: turning a Rust crate into a C-ABI library with pkg-config and a header

> cargo-c is a Cargo subcommand that builds a Rust crate as a static and dynamic C library, generates the header with cbindgen, writes a pkg-config file, and installs the result with the right paths. It exists so that a Rust project can expose a C API without hand-writing a build script.

**lu-zero/cargo-c** — build and install C-compatible libraries

- Repository: https://github.com/lu-zero/cargo-c
- Stars: 547 · Forks: 64
- Language: Rust
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/lu-zero-cargo-c

## The problem is the gap between a Cargo crate and a C consumer

Rust produces libraries that C programs cannot easily link. A Cargo crate builds a Rust library, and a C program wants a static or dynamic library, a header file with the declarations, and a way to find those files at link time. That last part is what pkg-config is for, and it is the part that nobody wants to hand-write. cargo-c is a Cargo applet to build and install C-ABI compatible dynamic and static libraries, and its stated job is to produce and install a correct pkg-config file, a static library and a dynamic library, and a C header to be used by any C and C-compatible software. The audience is a Rust project that has to be consumed from C, and the value is in removing the hand-written build glue rather than in the compilation itself.

## Installing it means one cargo install, with a fallback for OpenSSL

The installation section is a single command, since cargo-c is published on crates.io:

```sh
cargo install cargo-c
```

The README is precise about the toolchain. The rustc version supported is the same as the one supported by the cargo version embedded in the package version, or as set in the rust-version field, so the compiler and the tool are coupled. Building cargo-c itself requires the cargo build requirements, which the README lists as git, pkg-config on Unix, curl on Unix, and OpenSSL headers on Unix, the libssl-dev package on deb-based distributions. If the host-provided OpenSSL causes trouble building openssl-sys, there is a feature flag:

```sh
cargo install cargo-c --features=vendored-openssl
```

The manifest behind this shows how tight the coupling is. The version is 0.10.26+cargo-0.100.0, the cargo dependency is pinned at 0.100.0, and rust-version is set to 1.97. The cargo version is embedded in the package version precisely so a user can see which cargo behaviour the tool was built against.

## Three subcommands cover build, test and install

The manifest registers four binaries, one per subcommand, and the usage section gives the shape of each. Building the library, creating the .h header, and creating the .pc file looks like this:

```sh
$ cargo cbuild --destdir=${D} --prefix=/usr --libdir=/usr/lib64
```

Adding the test run changes only the verb:

```sh
$ cargo ctest
```

And installing everything, which is the same as the build plus the install step, uses:

```sh
$ cargo cinstall --destdir=${D} --prefix=/usr --libdir=/usr/lib64
```

The `--destdir`, `--prefix` and `--libdir` trio is the packaging vocabulary of the autotools world, and it is the right one, because it is what lets a distribution packager stage the install into a temporary root and then repackage it. The status checklist in the README confirms all three commands are done, along with cargo applet support, so the binaries are also callable as `cargo-cbuild` directly rather than only through `cargo cbuild`.

## Header generation is cbindgen, and the include guard is yours to write

The C header is not written by hand. The build pulls in cbindgen 0.29.0 with default features turned off, and the README's setup instructions require you to add a cbindgen.toml file and fill it with at least the include guard, and probably set the language to C since it defaults to C++. That last point is a real trap for anyone who expects a C compiler to accept the header, because cbindgen's default output is C++ syntax. The C-API code itself is expected in a capi.rs file guarded by a feature flag, and the README shows the older cfg(cargo_c) attribute struck out in favour of `#[cfg(feature="capi")]`, so the feature name is the contract between your crate and the tool. The feature must always be present in Cargo.toml, since it is how cargo-c identifies a package that has to be built as a C library inside a workspace.

## Cargo workspaces need one deliberate ordering rule

There is one wrinkle that catches people using workspaces, and the README is explicit about it. You need a lib target, and if you are using a workspace the first member is the crate you want to export, which means you might have to add a "." member at the start of the list. A separate list of links points at real pull requests that made this change, in lewton and in rav1e, so the pattern is not theoretical. The second workspace detail is that you should not specify the crate-type yourself, since cargo-c adds the correct library target by itself. The README also says not to add staticlib to the crate-type list, with a note that this has been the case since Rust 1.38. The status section lists staticlib support and cdylib support as done, so the tool handles both output kinds rather than leaving it to you.

## A separate bindings crate is the supported alternative path

If you would rather keep the bindings in their own crate and do not need the headers to be generated for you, the README gives a shorter route. Add a capi feature, since it is used by cargo-c to identify packages that have to be built as C libraries within a workspace. Set the entry in package.metadata.capi.header.generation to false, which tells the tool not to run the header generator at all. And optionally override the path to the header to a custom one instead of the default. The configuration reference in the documentation covers the package.metadata.capi keys in full, and there is a separate page on header generation that covers both cbindgen and cheadergen, so there is a second generator available for projects that need it.

## The list of adopters says more than the feature list does

The Users section of the README names the projects that build with cargo-c, and the list is a reasonable proxy for what the tool handles well. It includes ebur128, gcode-rs, gst-plugins-rs, lewton, libdovi, libimagequant, librsvg, rav1e, rustls-ffi, sled, pathfinder, and udbserver. Two of those are worth pausing on. librsvg uses it, which is a case of a large C library consuming a Rust implementation, and rustls-ffi is a security-sensitive project that needs a stable C ABI surface. The repository also ships an example-project and an example-workspace, both excluded from the workspace in the manifest so they do not build with the main package, which is where a new user can see the smallest possible working setup.

## Where cargo-c is the wrong tool

Three limits are visible in the documentation. The first is that the whole design assumes the C side consumes the library through pkg-config. If your consumers use CMake or Meson without pkg-config support, the .pc file is of limited use and you will be hand-writing the find module, which is much of the work cargo-c was meant to remove. The second is the coupling to Cargo's own version. The package version embeds the cargo version it was built against, and the manifest pins cargo 0.100.0 with rust-version 1.97, so the tool ages with the toolchain rather than independently, and you are choosing to build a Rust project against a specific cargo release when you install it. The third is that this is a packaging convenience, not a compatibility layer. It does not solve ABI stability across Rust versions, it does not generate safe bindings for the C consumer, and it does not handle a C API that needs to be versioned and kept stable over years. For that you want hand-written bindings, and the README's own users list includes crates such as rustls-ffi that maintain those separately.

## Conclusion

cargo-c fits a Rust project that wants to ship a C API to C consumers without maintaining a separate build system, particularly one already using Cargo workspaces and pkg-config on the consumer side. It is the wrong tool if you only need a Rust crate with bindings, since a normal library with a bindings crate is simpler, and it is the wrong tool if your C consumers build with something other than pkg-config, since the generated .pc file is the integration point and CMake or Meson users have to consume it differently. Before you adopt it, check that the crate version and the embedded Cargo version stay in step, because this release pins cargo 0.100.0 and the manifest sets rust-version 1.97, and read the troubleshooting documentation, since header generation and OpenSSL linking are where builds usually fail.

## FAQ

### How do I install cargo-c?

Run `cargo install cargo-c`, since it is published on crates.io. Building it needs git, pkg-config and curl on Unix, plus OpenSSL headers, and you can add `--features=vendored-openssl` if the host OpenSSL causes problems with openssl-sys.

### What does cargo cbuild actually produce?

It builds the library, creates the .h header and creates the .pc file. The README describes the output as a correct pkg-config file, a static library and a dynamic library, and a C header usable by any C or C-compatible software.

### How do I stop cargo-c from generating the header?

Set the entry in `package.metadata.capi.header.generation` to `false`. This is the route to take when you keep the bindings as a separate crate and supply your own header, and you can also override the path to the header to a custom one instead of the default.

### Do I need to set the crate-type to staticlib myself?

No. The README says not to specify the crate-type, because cargo-c adds the correct library target by itself. Static library and dynamic library support are both listed as done in the project status.

## Sources

- [Issues](https://github.com/lu-zero/cargo-c/issues)
- [License: MIT](https://github.com/lu-zero/cargo-c/blob/master/LICENSE)
- [lu-zero/cargo-c on GitHub](https://github.com/lu-zero/cargo-c)
- [README](https://github.com/lu-zero/cargo-c/blob/master/README.md)
- [Releases](https://github.com/lu-zero/cargo-c/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/lu-zero-cargo-c
