Comparing Rust to Carbon
LWN.net needs you!Without subscribers, LWN would simply not exist. Please consider signing up for a subscription and helping to keep LWN publishing.
Safe, ergonomic interoperability between Rust and C/C++ was a popular topic at RustConf 2025 in Seattle, Washington. Chandler Carruth gave a presentation about the different approaches to interoperability in Rust and Carbon, the experimental "(C++)++" language. His ultimate conclusion was that while Rust's ability to interface with other languages is expanding over time, it wouldn't offer a complete solution to C++ interoperability anytime soon — and so there is room for Carbon to take a different approach to incrementally upgrading existing C++ projects. His slides are available for readers wishing to study his example code in more detail.
Many of the audience members seemed aware of Carbon, and so Carruth spent relatively little time explaining the motivation for the language. In short, Carbon is a project to create an alternative front-end for C++ that cuts out some of the language's more obscure syntax and enables better annotations for compiler-checked memory safety. Carbon is intended to be completely compatible with C++, so that existing C++ projects can be rewritten into Carbon on a file-by-file basis, ideally without changing the compiler or build system at all. Carbon is not yet usable — the contributors to the project are working on fleshing out some of the more complex details of the language, for reasons that Carruth's talk made clear.
"It's always a little exciting to talk about a non-Rust programming language
at RustConf,
" Carruth began, to general laughter. He has worked in C++ for
many years, and has been working on Carbon since the project started in 2020.
Currently, he is paid for his work on Carbon as part of Google's languages and
compilers team.
He briefly showed some research from Google indicating that the
majority of the security vulnerabilities it deals with could have been
prevented by memory-safe languages, but he didn't spend too long on it because
he expected the audience of RustConf to be well-aware of the benefits of memory
safety.
The thing is, there is a lot of existing software in the world written in C and C++. There is no magic wand to make that software go away. Migrating any of it to memory-safe languages will require those languages to integrate with the rest of the existing software ecosystem, he said. Interoperability is not just nice to have — it's a key part of what makes adopting memory-safe languages work.
Rust already has several tools to make interoperating with C/C++ code feasible. Carruth listed Rust's native foreign-function interface, bindgen and cbindgen, the cxx crate, and Google's own Crubit. But he claimed that none of these are really good solutions for existing C++ software. He defined software as existing on a spectrum between "greenfield" (new code, not tightly coupled to C++, with strong abstraction boundaries) and "brownfield" (tightly coupled to existing C++, with a large API surface). Greenfield software is relatively easy to port to Rust — it can be moved one module at a time, using existing binding tools. Brownfield software is a lot harder, because it can't be easily decomposed, and so the interface between code written in C++ and code written in Rust has to be a lot more complex and bidirectional.
The question, Carruth said, is can Rust ever close the gap? He doesn't think so
— or, at least, not soon and not without a monumental effort. But Rust is not
the only approach to memory safety. Ideally, existing C++ code could be made
memory-safe in place. Lots of people have tried that, but "the C++ committee
is probably not going to do it
". There's no way to successfully add memory
safety to C++ as it is, he said.
There are several languages that have managed a transition away from a base language into a more capable, flexible successor language, though: TypeScript is an evolution of JavaScript, Swift is an evolution of Objective-C, and C++ itself is an evolution of C. Carruth thinks that Carbon could be a similar evolution of C++ — a path to incremental migration toward a memory-safe language, prioritizing the most entrenched brownfield software. Rust is coming at the problem of memory safety from the greenfield direction, he said, and Carbon is coming at it from the other side. That makes Rust and Carbon quite different languages.
A closer look
The real focus of his talk was on showing where those differences are, and where
he thinks each language can learn from the other. The syntaxes of Rust and Carbon are
"not wildly different
"; the differences he wanted to focus on were more
abstract. For example, in Rust, a compilation unit is an entire crate,
potentially composed of several modules. Therefore, it's allowed for
modules to reference each other in a cyclic way, and that just works.
That isn't something Carbon can
support because "existing C++ code is often oddly dependent
" on the ability to
compile individual files separately. So, Carbon inherits C++'s model, complete
with forward declarations, (optional) separate header files, and more complexity
in the linker. This makes Carbon's model more complex, but that complexity
doesn't come from nowhere — "it comes from C++
".
Another example is the difference between traits and classes. Rust traits and
Carbon classes are not that different, syntactically — Carbon just writes
methods inside the struct definition, while Rust writes them separately — but
they have major conceptual differences. Carbon has to handle inheritance,
virtual functions, protected fields, and so on. "This stuff is complexity
that Rust just doesn't have and doesn't have to deal with.
" Carbon wants to
meet C++ APIs where they are, he said.
One can even inherit across the
C++/Carbon boundary.
This sort of difference is pervasive, he said, and comes up in all parts of the language. Operator overloading, generics, and type conversions are all more complex in Carbon. Why do it this way? Why is all of this additional complexity worth it? To explain, he showed an example of a hypothetical but not unusual C++ API:
int EVP_AEAD_CTX_seal_scatter(
const EVP_AEAD_CTX *ctx,
std::span<uint8_t> out,
std::span<uint8_t> out_tag,
size_t *out_tag_len,
std::span<const uint8_t> nonce,
std::span<const uint8_t> in,
std::span<const uint8_t> extra_in,
std::span<const uint8_t> ad);
The example was adapted from a real function in the BoringSSL cryptography library. Each std::span is a combination of a pointer and a length. The main problem with faithfully representing it in Rust is actually not visible in the code itself; the documentation for this function explains that out must either be the same pointer as in, or a completely non-overlapping piece of memory. When the pointers are the same, the function encrypts a given plaintext input buffer in place. Otherwise, the encrypted output is written to the output buffer without disturbing the input buffer. None of the other pointers are supposed to alias.
Carbon is still a work in progress, but the current plan for expressing APIs like this in a machine-checkable way is to use "alias sets". These would be annotations showing which pointers are permitted to alias each other, and which ones aren't. The resulting Carbon code might look like this:
fn EVP_AEAD_CTX_seal_scatter[^inout]( ctx: const EVP_AEAD_CTX ^*, out: slice(u8 ^inout), out_tag: slice(u8 ^), out_tag_len: u64 ^*, nonce: slice(const u8 ^), input: slice(const u8 ^inout), extra_input: slice(const u8 ^), ad: slice(const u8 ^)) -> i32;
Here inout is a name given to a particular alias set, and used to annotate out and input. All of the other pointers in the function signature don't have an alias set specified, so the compiler would ensure they can't alias.
Trying to represent this API in Rust just doesn't work. The language never lets mutable references alias each other, so you end up having to have two separate wrapper functions with different signatures for the in-place case and the copying case. Rewriting the module that contains this function in Rust would become a complex process, intermingling the simple translation of the actual code with the refactoring of the interface.
The power of Carbon for interoperability, Carruth said, is that it lets you decouple these things and do them as small, separate steps. He showed another example of a C++ program that was actually memory-safe, but that wasn't compatible with Rust's lifetime analysis. No computerized analysis of memory safety can ever be perfect, so Carbon presumably won't be able to do much better here — but in Carbon, patterns that the compiler cannot prove to be memory safe can be turned into a warning instead of an error.
This focus on meeting C++ where it is makes Carbon a different language. It ends up being specially tailored to interoperability and gradual migration, which isn't free. This makes the language more complex than it could be otherwise, and Carruth doesn't think that's the right tradeoff for every language. But if the goal is to have memory-safe software throughout the software ecosystem, he thinks that there needs to be room for Rust and Carbon both. This isn't a competition between languages; it's two different languages working together to cover the widely divergent needs of different projects.
| Index entries for this article | |
|---|---|
| Conference | RustConf/2025 |