# dtolnay/cxx: A Safer FFI Boundary Between Rust and C++

> The cxx crate replaces bindgen-style unsafe C bindings with a single bridge module that both code generators read, so the Rust side can be audited as safe. Here is how the mechanism works, how to install it, and where it stops being the right tool.

**dtolnay/cxx** — Safe interop between Rust and C++

- Repository: https://github.com/dtolnay/cxx
- Website: https://cxx.rs
- Stars: 6,834 · Forks: 428
- Language: Rust
- License: Apache-2.0
- Published: 2026-09-22 · Updated: 2026-09-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/dtolnay-cxx

## The Problem cxx Solves at the FFI Boundary

Calling C++ from Rust through bindgen or cbindgen produces C-style bindings that the compiler cannot check against the other language's rules. The cxx README is direct about this: the library provides a safe mechanism for calling C++ from Rust and Rust from C++, "not subject to the many ways that things can go wrong when using bindgen or cbindgen to generate unsafe C-style bindings." The audience is the engineer who already has a C++ codebase, or a Rust codebase, and needs the two to talk without turning the Rust side into a pile of unsafe blocks.

The README also draws the boundary of the claim carefully. It does not say the C++ is safe: "100% of C++ code is unsafe." What it says is that under this model, auditing just the C++ side would be sufficient to catch all problems, meaning the Rust side can be 100% safe. That is the whole pitch, and it is a narrower one than a generic binding generator makes. If your C++ uses templates or macros that no hand-written signature can describe, cxx does not pretend to cover it.

## One Bridge Module, Two Code Generators

The mechanism is a single Rust module annotated with #[cxx::bridge]. Inside it you declare three kinds of items, as the README lists: shared structs whose fields are visible to both languages, opaque types whose fields are secret from the other language, and functions implemented in either language. Because both sides are declared together, the code generators get a complete picture of the boundary and can run static analyses against the types and signatures.

If the analysis passes, cxx emits matching extern "C" signatures on both sides plus static assertions that the build process verifies later. On the Rust side the generator is an attribute procedural macro. On the C++ side it is a Cargo build script when Cargo drives the build, or the cxxbridge-cmd command line tool when the build is Bazel or Buck; that tool writes out the header and source file. The README states the resulting bridge operates at zero or negligible overhead: no copying, no serialization, no memory allocation, no runtime checks.

Types can cross using either language's native forms. The README gives the pattern: manipulating a C++ string from Rust makes its len() method a call to C++'s size(), and manipulating a Rust string from C++ makes its size() call Rust's len(). Box maps to std::unique_ptr and Vec to std::vector, in any combination, based on builtin bindings for key standard library types. Opaque types cannot cross by value at all, only behind an indirection such as a reference, a Rust Box, or a UniquePtr.

## Installing cxx and Running the Blobstore Demo

The README gives the dependency pair for a Cargo project. Note that cxx-build is a build dependency, not a normal one, because it runs the C++ code generator during the build.

```toml
[dependencies]
cxx = "1.0"

[build-dependencies]
cxx-build = "1.0"
```

The README states the compiler support requirement as rustc 1.88+ and C++11 or newer. The repository's Cargo.toml sets rust-version = "1.88" and edition = "2024", and the default feature set is std plus cxxbridge-flags/default, which the manifest comments as c++11. Features named "c++14", "c++17" and "c++20" exist if you need a newer standard on the C++ side.

The fastest way to see the whole thing work is the demo directory, which the README describes as a runnable Rust application that calls an existing C++ client for a blobstore with a put operation for discontiguous buffer uploads. From that directory:

```bash
cargo run
```

The bridge module in the README declares a shared BlobMetadata struct with size and tags fields, an opaque MultiBuf type implemented in Rust, an unsafe extern "C++" block that includes demo/include/blobstore.h, an opaque BlobstoreClient, and functions including new_blobstore_client, put, tag and metadata. The C++ declarations live in demo/include/blobstore.h and demo/src/blobstore.cc; the Rust side lives in demo/src/main.rs with a demo/build.rs script. If you want to read the generated code rather than run it, the README points at two commands, the first requiring cargo-expand:

```console
cargo expand --manifest-path demo/Cargo.toml
cargo run --manifest-path bridge/cmd/Cargo.toml -- demo/src/main.rs
```

The first prints the Rust code generator output; the second runs the C++ generator against the demo's main.rs and prints the header and source it would produce.

## Where cxx Stops Being the Right Tool

The bridge only knows the signatures you write. Anything the C++ side does with templates, overload resolution or macros has to be flattened into a concrete function before cxx can see it, and the README does not claim otherwise. A header-only library built entirely around template specialization is a poor fit; you will end up writing a C++ shim that exposes plain functions, at which point you are maintaining two boundaries instead of one.

The safety claim has a second edge. Auditing just the C++ side is sufficient only if the bridge declarations are accurate, and cxx checks that with static assertions against the included header. The README notes the code generators "don't read it but it gets #include'd and used in static assertions to ensure our picture of the FFI boundary is accurate." If the header and the bridge drift apart in a way the assertions do not catch, that guarantee is weaker than it looks. Keeping the include! path and the declared signatures in step is manual work.

Opaque types cannot cross by value, which is a real constraint on API shape, not a formality. If your C++ API returns objects by value and you want them on the Rust side, you need an indirection or a shared struct that copies the fields you care about. The README lists the allowed indirections and stops there. There is also no documented rollback story for a bridge change: the README covers the forward path from declaration to generated code, and says nothing about reverting a boundary that has already shipped in a binary.

## cxx Against bindgen and cbindgen

The comparison that matters is with the generators cxx names in its own opening paragraph. bindgen reads C or C++ headers and emits Rust declarations; cbindgen reads Rust and emits a C or C++ header. Both work in one direction from an existing source of truth, and both produce bindings the compiler treats as unsafe on the Rust side. Neither has a static picture of both sides at once, because neither is given one.

cxx inverts that. The bridge module is the source of truth, and both generators are driven from it. That is why cxx can emit static assertions and why the README can claim the Rust side stays safe. The cost is that you write the boundary by hand instead of pointing a tool at a header. For a large existing C API, bindgen will get you a working binding in minutes and cxx will not; for a boundary you intend to audit and keep safe, cxx is the one that gives you something to audit against.

A second alternative is a plain extern "C" layer you write yourself, which is essentially what cbindgen output looks like. It has no build-time dependency on a code generator and no version coupling, but you own every invariant by hand. cxx's Cargo.toml pins cxxbridge-macro and cxxbridge-flags to the exact same version as the crate, so the generator and the runtime cannot drift apart within a build.

## Maintenance, Version Coupling and Licence

The repository is not archived, and the last push was on 2026-09-12. That is recent enough to treat the project as maintained, and the release history supports it: 1.0.200 on 2026-09-04, 1.0.201 on 2026-09-11, and 1.0.202 on 2026-09-12, all within the same 1.0.x line. The version numbers move in the third component, which is what you want from a dependency you have already integrated.

The upgrade cost is mostly the version pinning. In the repository's own Cargo.toml, cxxbridge-macro and cxxbridge-flags are declared as =1.0.202, and the manifest carries a commented section that disallows an incompatible version appearing in the same lockfile by pulling cxx-build and cxxbridge-cmd into a cfg(any()) target. The practical consequence is that a partial upgrade, where the cxx crate moves but cxxbridge-macro does not, is designed to fail rather than silently mismatch. Expect to bump the crate and the build dependency together.

The licence is dual: the crate manifest says "MIT OR Apache-2.0", and the repository root carries LICENSE-APACHE and LICENSE-MIT. Choose whichever of the two you prefer, since the OR lets you pick. That is the extent of what the README supports; whether either licence fits your organisation's policy is a question for your own review, not something the README answers.

## Conclusion

Adopt cxx when a C++ library must be called from Rust and you are willing to keep the boundary declarations in one #[cxx::bridge] module; the payoff is that auditing the C++ side is meant to cover the whole boundary, per the README. Do not adopt it if the C++ API you need is template-heavy or macro-driven, because the bridge only understands the signatures you write by hand. Before committing, verify three things in your own tree: that rustc 1.88 or newer and a C++11-or-newer compiler are available, that your build system has a path through cxx-build or cxxbridge-cmd, and that every type crossing the boundary is expressible as a shared struct, an opaque type behind an indirection, or one of the builtin standard library bindings.

## FAQ

### What is the cxx crate used for?

It provides a safe mechanism for calling C++ code from Rust and Rust code from C++, as an alternative to bindgen or cbindgen style C bindings. The Rust side is intended to be 100% safe, with auditing of the C++ side sufficient to catch all problems.

### Is cxx the same as C++?

No. cxx is the name of the Rust crate dtolnay/cxx, which generates an FFI bridge between Rust and C++. The C++ language itself is separate, and the README notes that 100% of C++ code is unsafe regardless of the bridge.

### Where can I find cxx examples?

The README points to https://cxx.rs for a tutorial, reference material and example code, and to the demo directory of the repository for a runnable blobstore example. The complete demo source is listed as demo/src/main.rs, demo/build.rs, demo/include/blobstore.h and demo/src/blobstore.cc.

### What does the name cxx mean in the context of this project?

The README does not explain the name. What it does explain is that the crate provides safe interop between Rust and C++, with the FFI boundary declared together in one Rust module annotated with #[cxx::bridge].

## Sources

- [dtolnay/cxx on GitHub](https://github.com/dtolnay/cxx)
- [License: Apache-2.0](https://github.com/dtolnay/cxx/blob/master/LICENSE)
- [Project website](https://cxx.rs)
- [README](https://github.com/dtolnay/cxx/blob/master/README.md)
- [Releases](https://github.com/dtolnay/cxx/releases)

---

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