Carcinize (carcinize) is a migration tool that automatically converts existing C++ libraries to Rust libraries containing embedded C++ code via inline_cpp!.
Carcinize makes it possible to migrate C++ libraries to Rust incrementally. Carcinize generates a Rust crate where:
inline_cpp! blocks.cpp_api_from_rust targets.The generated Rust target will result in the same API and use the same implementation as the original C++ target, but will be entirely within a Rust source file. You can then refactor functions one at a time from embedded C++ into idiomatic Rust while verifying behavior with tests at every step.
Carcinize can be run directly on a cc_library target in your workspace (the standard workflow for Bazel users) or in standalone mode on source files (for non-Bazel environments, automated developer tooling, and AI agents).
To migrate a cc_library in your workspace, run:
bazel run //google_internal/carcinize:carcinize -- //path/to/pkg:my_target
Carcinize will:
//path/to/pkg/my_target_rs.rs.//path/to/pkg/BUILD to replace the cc_library with a rust_library_with_embedded_cpp and re-export the original target name via cpp_api_from_rust.After the command completes, verify the generated target with your unit tests:
bazel test //path/to/pkg:...
For developers in non-Bazel environments (such as Cargo or CMake projects), as well as automated developer tooling, AI agents, and custom scripts, Carcinize provides a standalone CLI mode that operates directly on C++ headers and source files without evaluating Bazel queries or mutating BUILD files:
bazel run //google_internal/carcinize:carcinize -- \ --headers path/to/header.h \ --srcs path/to/source.cc \ --out path/to/output_rs.rs
Some C++ libraries contain features that Crubit does not yet bind automatically, such as C++ function templates or unbindable types.
By default, Carcinize allows incomplete migrations by partitioning unsupported declarations into companion global_cpp! blocks in the generated Rust crate so the migrated crate compiles out-of-the-box.
If you instead want to strictly require complete Crubit binding generation and fail if any unsupported C++ declarations are present, pass --require_complete_migration (or --require-complete-migration):
bazel run //google_internal/carcinize:carcinize -- \ --require_complete_migration //path/to/pkg:my_target
In standalone mode:
bazel run //google_internal/carcinize:carcinize -- \ --require_complete_migration \ --headers path/to/header.h \ --srcs path/to/source.cc \ --out path/to/output_rs.rs
When --require_complete_migration is enabled and unsupported C++ declarations are present, the Crubit bindings build fails with an error for each unsupported declaration (for example, Function templates are not yet supported), and Carcinize halts.
By default, Carcinize partitions declarations:
struct definitions.inline_cpp! blocks.global_cpp! blocks in the generated Rust file.Consider a C++ header containing both a standard function and a C++ template:
cs/file:examples/cpp/carcinize/math_utils.h content:^namespace\s+math\s*\{[\s\S]*?^\}\s*//\s*namespace\s+math
When migrated, Carcinize generates math_utils_rs.rs:
cs/file:examples/cpp/carcinize/math_utils_rs.rs content:^\s*global_cpp!\s*\{[\s\S]*?^\}\s*pub\s+mod\s+math\s*\{[\s\S]*?^\}
rust_library_with_embedded_cpp extracts the companion C++ code from global_cpp! and compiles it into a companion C++ header. cpp_api_from_rust re-exports both the Rust bindings and the companion header back to C++ callers.
Clamp<T>).global_cpp! with concrete types inside inline_cpp! blocks (for example, math::Clamp(val, min, max)).Vector2, DotProduct).The generated target compiles immediately without manual intervention.
Migrating fallback declarations to pure Rust is optional. If you choose to remove C++ dependencies entirely, you can replace the fallback global_cpp! declarations with Rust implementations over time using one of these strategies:
cs/file:examples/cpp/carcinize/math_utils_rs.rs function:clamp
NOTE:
cpp_api_from_rustdoes not currently generate C++ function templates from Rust generic functions. If existing C++ callers still require the C++ template, keep the declaration in theglobal_cpp!block (or provide concrete C++ wrappers as shown below) until downstream C++ callers are migrated.
inline_cpp!cs/file:examples/cpp/carcinize/math_utils_rs.rs function:clamp_i32
Once all declarations in global_cpp! have been ported to Rust or are no longer needed, delete the global_cpp! block from the generated file and remove the corresponding C++ dependencies from deps_of_cc_library in your BUILD file.
When performing an incremental migration across multiple CLs, fallback global_cpp! blocks can be safely checked in and maintained while downstream callers or unsupported features are migrated over time.
However, if your team wants to enforce complete migration in a single change (or prevent unmigrated C++ declarations from being accidentally committed during local experimentation), you can rename the fallback global_cpp! block to DO_NOT_SUBMIT_CPP_DECL! by hand. C++ extraction treats it the same as global_cpp!:
DO_NOT_SUBMIT_CPP_DECL! { namespace math { template <typename T> T Clamp(T val, T min, T max) { return val < min ? min : (val > max ? max : val); } } // namespace math }
Piper presubmit checks will block submitting changes containing DO_NOT_SUBMIT blocks while still allowing local compilation and testing until all fallbacks are resolved. The build rules also emit compile-time warnings if declarations inside fallback blocks become natively supported by Crubit.
Consider a C++ library target:
cs/file:examples/cpp/carcinize/BUILD symbol:point_before
Carcinize updates the BUILD file to define a rust_library_with_embedded_cpp target for the migrated Rust library:
cs/file:examples/cpp/carcinize/BUILD symbol:point_rs
And re-exports the library back to C++ callers under the original target name using cpp_api_from_rust:
cs/file:examples/cpp/carcinize/BUILD symbol:\bpoint\b
rust_library_with_embedded_cpp (:point_rs): Compiles the generated Rust crate and formats/extracts embedded C++ blocks.deps_of_cc_library: Lists C++ dependencies required by C++ headers used in inline_cpp! and global_cpp! blocks.cpp_api_from_rust (:point): Exposes the Rust library back to C++ callers under the original target name, so downstream dependencies require no changes.For a C++ library like:
cs/file:examples/cpp/carcinize/point.h content:^namespace\s+geometry\s*\{[\s\S]*?^\}\s*//\s*namespace\s+geometry
Carcinize generates point_rs.rs:
cs/file:examples/cpp/carcinize/point_rs.rs content:^\s*global_cpp!\s*\{[\s\S]*?^\}\s*pub\s+mod\s+geometry\s*\{[\s\S]*?^\}
Once Carcinize generates the initial Rust scaffolding, you can incrementally refactor the crate into idiomatic Rust:
bazel test //path/to/pkg:... to confirm the generated target compiles and passes existing unit tests.inline_cpp! blocks one function at a time with pure Rust implementations.::ffi_11::c_int) with standard Rust types (such as i32), and derive standard traits (Debug, PartialEq, Default).global_cpp! blocks and deps_of_cc_library from your BUILD file.For example, when rewriting functions in step 2, convert embedded C++:
cs/file:examples/cpp/carcinize/point_rs.rs function:GetX
Into pure, idiomatic Rust:
cs/file:examples/cpp/carcinize/point_refactored.rs function:GetX
Carcinize and inline_cpp! use Crubit under the hood, so any Crubit error can occur during migration. See crubit.rs/errors and Inline C++ in Crubit for related diagnostic guidance.
<target>_rs already exists {#error_collision}If a target named <target>_rs already exists in the BUILD file, Carcinize aborts to avoid overwriting existing code:
Error: Target //geometry:point_rs already exists. Aborting to prevent collisions.
Rename or remove the existing target before running Carcinize.
When --require_complete_migration is enabled and any unbindable declarations are encountered, the Crubit bindings build fails with an error for each unsupported declaration (for example, Function templates are not yet supported), and Carcinize halts.
To proceed with migrating partially supported libraries without halting, omit --require_complete_migration to allow Carcinize to route those declarations into companion global_cpp! fallback blocks automatically.
If Clang fails to find #include headers during compilation:
error: 'third_party/absl/strings/str_cat.h' file not found
#include "third_party/absl/strings/str_cat.h"
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Ensure the target exporting those headers is listed in deps_of_cc_library in your rust_library_with_embedded_cpp rule (or in deps of the original cc_library).
If C++ code inside inline_cpp! or global_cpp! has unbalanced braces (for example, in macros or raw strings), the Rust compiler reports an error when parsing token trees:
error: unexpected closing delimiter: `}` --> math_utils_rs.rs:25:5 | 25| } | ^ unexpected closing delimiter
Ensure all opening and closing braces within the macro block are properly balanced.