LUNAROPS · OPERATIONAL UPLINK 100% UPTIME 1,247d POSTS 893 JEFF.MOON@LUNAROPS.DEV UTC --:--:--

Carbon: Google's Bet on a C++ Successor

carboncppsystems-programminggooglecompiledmemory-safetygenerics
Contents

C++ has survived 40 years of engineering criticism, countless “replacement” attempts, and the relentless rise of memory-safe languages. It is still the dominant language for systems programming, game engines, embedded firmware, compilers, and high-frequency trading infrastructure. If you write performance-critical code for a living, you likely live in C++.

In July 2022, Chandler Carruth stepped onto the stage at CppNorth and announced Carbon — not another language that wants to replace C++, but a “successor language” designed to interoperate deeply with existing C++ codebases while offering the kind of ergonomics and safety guarantees that C++ simply cannot retrofit onto itself.

This post is a technical deep-dive into what Carbon is, where it stands in 2025/2026, what it gets right, where it remains vaporware, and whether it has any realistic shot at changing the C++ ecosystem. The short answer: Carbon is genuinely interesting, the C++ interop story is uniquely compelling, and it is absolutely not production-ready. Read on for the long answer.


What Carbon Is — And What It Is Not

Carbon is an open-source, experimental programming language being developed primarily by Google engineers with the explicit goal of being a successor to C++, not a replacement for it. The distinction matters enormously.

A replacement language (think Rust, Zig, or D) asks you to start a new codebase in a new language and gradually migrate. A successor language, in Carbon’s framing, interoperates bidirectionally with existing C++ code — so you can introduce Carbon into an existing 10-million-line C++ codebase one file or one library at a time, calling C++ from Carbon and Carbon from C++, without rewriting the world first.

Chandler Carruth (a Google engineer who has spent years on LLVM, Clang, and C++ performance) described this in his CppNorth talk with a deliberate analogy: Carbon is to C++ what Kotlin is to Java. Kotlin didn’t ask Java shops to abandon the JVM or their existing Java codebases. It offered a better language that compiled to the same bytecode and called Java libraries directly. That incremental migration path is what enabled Kotlin to actually succeed in the Android ecosystem.

The same philosophy drives Carbon’s design. The premise is that you cannot fix C++ from within C++.

The Argument Against Fixing C++ From Within

To understand why Google created Carbon rather than just submitting proposals to the C++ standards committee, you need to understand three specific problems that Carruth and the Carbon team consider fundamental rather than fixable:

ABI Stability. The C++ Application Binary Interface is effectively frozen. The way structs are laid out in memory, how vtables work, how names are mangled — these are locked in because changing them would break binary compatibility with decades of compiled libraries. This means C++ cannot adopt changes to its fundamental data structures (like improving std::string or std::vector) without either breaking the ABI or maintaining multiple versions in parallel. Carbon starts with a clean ABI slate.

The Committee Process. The ISO C++ committee process is famously slow and conservative. Features go through years of proposals, revisions, counter-proposals, and votes. Controversial features (contracts, reflection, pattern matching, improved generics) take a decade or more to land, if they ever do. This isn’t a criticism of the people involved — the committee has to maintain backward compatibility across millions of codebases and decades of deployed code. But it means the language cannot move at the speed that modern software needs.

Backwards Compatibility Debt. C++ inherits from C, which means it carries 50 years of accumulated syntax, implicit conversions, undefined behavior edge cases, and design decisions made when 16KB was a lot of RAM. Every attempt to modernize C++ (smart pointers, move semantics, ranges, concepts) has to coexist with the old way of doing things. You can’t remove printf without breaking millions of programs. You can’t change how integer promotion works without breaking embedded code. Carbon can make different defaults because it doesn’t have to be C++.

The Carbon team’s conclusion: the right approach is to design a new language from scratch, give it C++ interoperability as a first-class feature, and give C++ engineers a migration path. Everything else follows from this.


Current State: What’s Real in 2025/2026

Here is the honest picture of where Carbon stands as of early 2026.

Carbon is pre-0.1. The project has not released version 0.1. The team’s stated milestone for 0.1 is roughly “a language that is good enough that we’d consider using it for real projects” — and they are not there yet. The GitHub repository (github.com/carbon-language/carbon-lang) is active and public, with regular commits from a small team of primarily Google engineers, but the language is explicitly experimental.

Do not use Carbon in production. The Carbon team says this explicitly and repeatedly. The language spec is changing. APIs break. The compiler is incomplete. If you write Carbon today, expect your code to stop compiling after a toolchain update. This is a research and experimentation phase, not a deployment phase.

The toolchain has two components:

The Carbon Explorer (carbon-explorer) is a reference interpreter — it implements the Carbon language semantics for experimentation and exploration, but it is not an optimizing compiler. It is designed to test and demonstrate language behavior, not to produce fast binaries. Think of it as the canonical implementation of the spec, used to answer “what does this program mean” rather than “compile this to production code.”

The Carbon Toolchain (carbon-toolchain) is the real compiler under development. It targets LLVM and is intended to eventually produce native code competitive with Clang-compiled C++. As of 2025/2026, it compiles a meaningful subset of the language, but it is far from complete. Basic programs compile and run. Complex generics, full C++ interop, and many standard library features are not yet fully implemented.

What is implemented:

  • Basic types: integers, floats, booleans, strings
  • Functions (fn keyword), variables (var and let)
  • Basic classes with methods
  • Package and import system
  • Control flow: if/else, while, for
  • Tuples
  • Interfaces (the foundation of generics)
  • Basic pattern matching

What is planned but not fully implemented:

  • Full bidirectional C++ interoperability (partially implemented, not complete)
  • Full checked generics with complex constraints
  • Complete standard library
  • Memory safety (“safe Carbon” as a dialect)
  • Complete pattern matching
  • Destructuring
  • Error handling model (exceptions vs values — still being designed)
  • Full toolchain integration for large projects

The GitHub repository has thousands of stars and regular activity. The language specification is written in Markdown in the repo itself and is surprisingly readable — if you want to understand what Carbon is supposed to be, reading the spec directly is worthwhile.


Design Goals: The Five Pillars

The Carbon team has been explicit about their design goals. They are roughly ordered by priority:

1. Performance Matching C++

Carbon compiles to native code via LLVM, the same backend used by Clang. The goal is that a Carbon program doing the same thing as a C++ program should produce essentially identical machine code — same performance, same memory layout, same calling conventions. Carbon is not adding a garbage collector, a runtime, or any overhead that you do not explicitly opt into.

This is table stakes for a C++ successor. If Carbon were 2x slower than C++, the entire premise collapses. The LLVM backend ensures that Carbon benefits from decades of optimization research that has gone into LLVM.

2. Bidirectional C++ Interoperability

This is Carbon’s signature feature and the thing that distinguishes it from Rust, Zig, or D. The goal: you can call C++ from Carbon, and you can call Carbon from C++, in the same binary, without writing wrapper layers or FFI glue.

The Cpp. namespace in Carbon lets you import C++ headers and use C++ types and functions directly. You can pass C++ objects to Carbon functions. You can pass Carbon objects to C++ functions. The interop is designed to be zero-cost where possible.

This is the Kotlin-for-Java moment in Carbon’s design. It is the thing that might actually let Carbon into real codebases.

3. Safe by Default, Unsafe by Opt-Out

Carbon’s safety model is not Rust’s borrow checker — not yet and possibly not ever in the same form. The current design has “safe Carbon” and “unsafe Carbon” as distinct modes, where “safe Carbon” enforces bounds checking, disallows raw pointer arithmetic, and prevents obvious memory unsafety. “Unsafe Carbon” (within unsafe blocks, similar to Rust) allows low-level operations needed for systems code.

The memory safety roadmap (discussed in detail later) is more aspirational than concrete, but the direction is clear: Carbon wants to be memory-safe by default, with unsafe operations as explicit, visible, auditable choices.

4. Modern Generics

C++ templates are powerful but deeply flawed from an ergonomics and tooling perspective. Carbon’s generics system is checked generics — constraints are verified at the definition site, not at the instantiation site. This gives you comprehensible error messages, better IDE tooling, and clearer code. This is covered in detail in its own section.

5. Modern Developer Experience

Carbon’s syntax is designed to be unambiguous, consistent, and tool-friendly. No most-vexing-parse. No implicit integer promotion surprises. No undefined behavior from signed integer overflow (Carbon’s signed integers wrap rather than UB). Headers are replaced with a package system. The syntax is explicitly designed to be parseable without a preprocessor.


Language Design: What Carbon Code Looks Like

Let’s get concrete. Here is Carbon syntax based on the public specification and examples from the repository.

Packages and Imports

Carbon replaces headers with a package system. Every source file belongs to a package, and you import packages rather than including headers.

// File: my_library/algorithms.carbon
package MyLibrary api;

// This is exported from the package
fn BinarySearch[T:! Ordered](haystack: Slice(T), needle: T) -> Optional(i32);
// File: my_app/main.carbon
package MyApp;

import MyLibrary;
import Cpp library "vector" as CppVector;

fn Run() {
  var v: CppVector.vector(i32) = {};
  v.push_back(42);
}

The package Foo api; declaration marks a file as defining the public API of a package. Implementation files use package Foo; without api. This is a much cleaner separation than C++ headers and is amenable to incremental compilation.

Functions

fn Add(x: i32, y: i32) -> i32 {
  return x + y;
}

fn Greet(name: String) {
  Print("Hello, {0}!", name);
}

// Return type deduced from return statement
fn Square(n: i32) -> i32 {
  return n * n;
}

Functions use fn, parameters are named with types after a colon (similar to Kotlin or Swift), and the return type follows ->. The syntax is consistent and unambiguous — no trailing return types, no decltype hacks.

Variables

fn Example() {
  // Mutable variable — var
  var count: i32 = 0;
  count = count + 1;

  // Immutable binding — let
  let pi: f64 = 3.14159;

  // Type inference
  var message: auto = "Hello";

  // Explicit type, no initializer
  var result: i32;
}

The var/let distinction is explicit and visible — there is no accidental mutability. auto for type inference works similarly to C++11’s auto.

Classes

Carbon’s class system differs from C++ in several important ways:

class Point {
  var x: f64;
  var y: f64;

  fn Distance[self: Self](other: Point) -> f64 {
    let dx = self.x - other.x;
    let dy = self.y - other.y;
    return Math.Sqrt(dx * dx + dy * dy);
  }
}

fn UsePoint() {
  var p1: Point = {.x = 0.0, .y = 0.0};
  var p2: Point = {.x = 3.0, .y = 4.0};
  let d = p1.Distance(p2);  // d == 5.0
}

Notice [self: Self] in the method signature — the receiver is explicit and named, not implicit. This is a deliberate design choice: there is no hidden this pointer magic. Methods are just functions with an explicit receiver.

Designated initializers (.x = 0.0, .y = 0.0) initialize structs by field name, similar to C99 designated initializers but as the primary initialization syntax.

Inheritance

Carbon supports single inheritance for interoperability with C++ class hierarchies, but the design encourages composition over inheritance and makes virtual dispatch explicit:

base class Shape {
  fn Area[self: Self]() -> f64;  // Abstract — no body
  fn Describe[self: Self]() {
    Print("Shape with area {0}", self.Area());
  }
}

class Circle extends Shape {
  var radius: f64;

  fn Area[self: Self]() -> f64 {
    return Math.Pi * self.radius * self.radius;
  }
}

The base class keyword explicitly marks a class as inheritable. Regular class declarations are final by default — you cannot inherit from them. This eliminates a common C++ footgun (forgetting to make a destructor virtual) and makes the class hierarchy explicit in the type system.

Control Flow

fn Classify(n: i32) -> String {
  if (n < 0) {
    return "negative";
  } else if (n == 0) {
    return "zero";
  } else {
    return "positive";
  }
}

fn CountDown(start: i32) {
  var i: i32 = start;
  while (i > 0) {
    Print("{0}", i);
    i = i - 1;
  }
  Print("Blast off!");
}

fn SumList(values: Slice(i32)) -> i32 {
  var total: i32 = 0;
  for (v: i32 in values) {
    total = total + v;
  }
  return total;
}

No syntactic surprises here. Control flow is intentionally familiar to C++ programmers.

Pattern Matching

Carbon is adding pattern matching, though the implementation is still evolving:

fn Describe(value: i32) -> String {
  match (value) {
    case 0 => { return "zero"; }
    case 1 | 2 | 3 => { return "small"; }
    case n if n < 0 => { return "negative"; }
    default => { return "large"; }
  }
}

Pattern matching extends to types, useful with sum types:

choice Result(T:! Type, E:! Type) {
  Success(T),
  Failure(E),
}

fn HandleResult(r: Result(i32, String)) {
  match (r) {
    case .Success(n) => { Print("Got: {0}", n); }
    case .Failure(msg) => { Print("Error: {0}", msg); }
  }
}

The choice type is Carbon’s algebraic data type — what Rust calls enum, Haskell calls a sum type, and C++ approximates with std::variant. This is critical for the error handling story.


The C++ Interoperability Story

This is Carbon’s most important and most differentiated feature. Let’s go deeper than the surface.

The Cpp. Namespace

Carbon can import C++ headers directly:

package MyApp;

import Cpp library "<vector>" as CppVec;
import Cpp library "<string>" as CppString;
import Cpp library "mylib/my_header.h";

fn UseCppTypes() {
  var v: CppVec.std.vector(i32) = {};
  v.push_back(1);
  v.push_back(2);
  v.push_back(3);

  var s: CppString.std.string = "hello";
  Print("Size: {0}", s.size());
}

The C++ STL is accessible directly from Carbon without wrapper layers. C++ objects live on the Carbon stack or heap, with their full C++ semantics — destructors still run, copy and move semantics work as expected.

Calling Carbon from C++

The other direction is equally important. Carbon functions can be marked for export to C++:

package MyCarbonLib api;

// This function is callable from C++
fn ProcessData(data: Slice(i32)) -> i32 {
  var sum: i32 = 0;
  for (x: i32 in data) {
    sum = sum + x;
  }
  return sum;
}

From C++:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// Generated header from Carbon toolchain
#include "my_carbon_lib.h"

int main() {
    std::vector<int> data = {1, 2, 3, 4, 5};
    int result = MyCarbonLib::ProcessData(
        carbon::Slice<int>(data.data(), data.size())
    );
    std::cout << result << std::endl;  // 15
}

The toolchain generates C++ headers for Carbon APIs, allowing C++ code to call into Carbon as if it were just another C++ library.

The Migration Path

The practical migration story for a hypothetical production C++ codebase looks like this:

  1. Identify a self-contained component — a utility library, a data processing module
  2. Rewrite it in Carbon, using import Cpp to maintain dependencies on other C++ components
  3. The Carbon component exports a C++ header, so the rest of the codebase sees no difference
  4. Gradually expand Carbon coverage, component by component

This is fundamentally different from how you migrate a C++ codebase to Rust. In Rust, you need to write FFI bindings, deal with the ownership model at the boundary, and often create explicit wrapper types. The boundaries are visible and painful. In Carbon, the boundaries are designed to be nearly invisible.

The Limits of the Interop Story

It is worth being honest about the current state of the interop implementation. As of 2025/2026, the C++ interop is partially implemented and actively being built out. The design is clear and the team has demonstrated early versions, but:

  • Template instantiation across the boundary is complex and not fully working
  • Some STL types have rough edges
  • C++ exceptions crossing into Carbon are not fully handled
  • SFINAE-heavy C++ code can be difficult to import cleanly

The design is sound. The implementation has significant work remaining.


Checked Generics: Carbon’s Most Important Innovation

If C++ interoperability is Carbon’s most important practical feature, checked generics may be its most important technical innovation. Understanding this requires understanding why C++ templates are problematic.

Why C++ Templates Are a Problem

C++ templates are Turing-complete metaprogramming via substitution. The compiler copies the template body and replaces type parameters with actual types. If anything goes wrong, the error happens at the instantiation site — where you used the template with specific types — not at the definition site — where the template was written.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// C++ template — no interface constraints
template<typename T>
void ProcessAll(const std::vector<T>& items) {
    for (const auto& item : items) {
        item.process();  // We HOPE T has a process() method
    }
}

struct NoProcessMethod {
    int x;
};

int main() {
    std::vector<NoProcessMethod> v = {{1}, {2}};
    ProcessAll(v);  // Error happens HERE, not where ProcessAll is defined
                    // The error message will be a 40-line template explosion
}

The error message from this in real C++ will reference internal implementation details of std::vector, name mangling, template expansion chains — things that are completely irrelevant to the actual problem, which is simply that NoProcessMethod doesn’t have a process() method.

C++20 Concepts improve this, but they are opt-in and compatibility requirements mean old code still works the old way.

Carbon’s Checked Generics

Carbon’s generics are checked at the definition site. To use a generic function, you must declare what interfaces the type parameters must satisfy. The compiler verifies those constraints when the generic is defined, and again when it is instantiated. Error messages point to the relevant location.

// First, define an interface
interface Processable {
  fn Process[self: Self]();
}

// Generic function — T must implement Processable
// The :! syntax means "generic parameter with constraint"
fn ProcessAll[T:! Processable](items: Slice(T)) {
  for (item: T in items) {
    item.Process();  // CHECKED: T is guaranteed to have Process()
  }
}

The :! syntax is Carbon’s notation for a checked generic parameter. T:! Processable means “T is a type that implements the Processable interface.” The compiler verifies at the point of ProcessAll’s definition that every use of T inside the function body is consistent with the Processable interface.

Implementing Interfaces with impl

Types implement interfaces with explicit impl declarations:

class Task {
  var name: String;
  var priority: i32;

  fn Process[self: Self]() {
    Print("Processing task: {0} (priority {1})", self.name, self.priority);
  }
}

// Explicit interface implementation
impl Task as Processable {
  fn Process[self: Self]() {
    Print("Processing task: {0} (priority {1})", self.name, self.priority);
  }
}

Or, since the method is already defined on the class, you can use a more compact form when the class body already satisfies the interface:

// Tell the compiler that Task already satisfies Processable
impl Task as Processable = Task;

Now ProcessAll works correctly:

fn Main() -> i32 {
  var tasks: [Task; 2] = [
    {.name = "Deploy", .priority = 1},
    {.name = "Monitor", .priority = 2},
  ];
  ProcessAll(tasks);
  return 0;
}

If you try to pass a type that doesn’t implement Processable, the error says exactly that — “type X does not implement interface Processable” — at the call site, not deep inside the generic function’s implementation.

Interface Composition

Interfaces can extend other interfaces:

interface Named {
  fn Name[self: Self]() -> String;
}

interface Printable {
  fn Print[self: Self]();
}

// Composed interface
interface NamedAndPrintable extends Named & Printable {}

Checked vs Template: A Side-by-Side

1
2
3
4
5
6
7
8
9
// C++ — template, errors at instantiation
template<typename Container>
auto Sum(const Container& c) {
    typename Container::value_type total{};
    for (const auto& v : c) {
        total += v;
    }
    return total;
}
// Carbon — checked generic, errors at definition
interface Addable {
  fn Add[self: Self](other: Self) -> Self;
  fn Zero() -> Self;
}

fn Sum[T:! Addable](items: Slice(T)) -> T {
  var total: T = T.Zero();
  for (item: T in items) {
    total = total.Add(item);
  }
  return total;
}

The Carbon version is more verbose up front, but:

  • Error messages are comprehensible
  • IDEs can show you what operations are available on T
  • The function’s contract is explicit and documented in code
  • Separate compilation is possible without header files

This is the same trade-off that Go generics and Rust traits make, and it is the right trade-off for large codebases.


Memory Safety: The Roadmap and the Reality

Memory safety is the elephant in the room for any C++ successor. Roughly 70% of security vulnerabilities in large C++ codebases (Chrome, Firefox, Windows, iOS) trace to memory safety bugs. This is not controversial. The question is what Carbon plans to do about it.

Current State: Not Memory Safe

Carbon is currently not memory safe. You can write Carbon code that has use-after-free, buffer overflows, and null pointer dereferences. The checked generics and type system improvements do not, by themselves, prevent memory unsafety. This is an important thing to understand clearly.

The Memory Safety Roadmap

The Carbon team has published a roadmap for memory safety that involves two tracks:

Track 1: Spatial Safety (Bounds Checking) The lower-hanging fruit is preventing out-of-bounds memory accesses. Carbon’s Slice type (equivalent to a bounds-checked span/view) is the idiomatic way to pass array-like data. Operations on slices are bounds-checked by default. The goal is that in “safe Carbon,” out-of-bounds accesses are caught at runtime (with a panic) or at compile time, not silently corrupted.

Track 2: Temporal Safety (Lifetime Safety) Temporal safety — preventing use-after-free, dangling pointers, and data races — is the harder problem. This is what Rust’s borrow checker solves. Carbon’s approach here is deliberately different from Rust’s, and more cautious.

The Carbon team is researching what they call “lifetime annotations” — a system that allows the compiler to reason about object lifetimes without requiring the full complexity of Rust’s borrow checker. The thinking is that Rust’s borrow checker, while correct, has a steep learning curve and rejects valid programs that a human programmer knows are safe. Carbon wants to find a point on the spectrum that is safer than raw C++ but more ergonomic than Rust.

Safe Carbon vs Unsafe Carbon

The mental model the team is working toward:

// "Safe Carbon" — this code is checked for memory safety
fn SafeFunction(data: Slice(i32)) -> i32 {
  // No raw pointers, no unsafe operations
  // Bounds checking enforced
  var sum: i32 = 0;
  for (x: i32 in data) {
    sum = sum + x;
  }
  return sum;
}

// "Unsafe Carbon" — escape hatch for systems code
unsafe fn UnsafeFunction(ptr: *i32, len: i32) -> i32 {
  // Raw pointer arithmetic allowed here
  // Programmer asserts correctness
  var sum: i32 = 0;
  var i: i32 = 0;
  while (i < len) {
    sum = sum + *(ptr + i);
    i = i + 1;
  }
  return sum;
}

The unsafe keyword makes the boundary between checked and unchecked code visible and auditable — the same approach as Rust and Swift. The difference from Rust is that “safe Carbon” does not require lifetime annotations in the same exhaustive way Rust does, though the design is still evolving.

Comparing to Rust’s Approach

Rust’s borrow checker is the state of the art in compile-time memory safety. It tracks ownership and lifetimes at compile time, eliminating use-after-free and data races without any runtime overhead. It works, but it requires a fundamentally different way of thinking about data structures and APIs.

Carbon’s approach, still in design, aims to be:

  • Safer than raw C++ (definite improvement on use-after-free and bounds checking)
  • Less ergonomically demanding than Rust’s borrow checker
  • Compatible with the patterns C++ engineers already use

Whether this is achievable — a useful intermediate point between “C++ unsafe” and “Rust safe” — is a genuine open question. The Carbon team’s thesis is that you can get 80% of the safety with 20% of the friction, especially for codebases that already follow good C++ practices. Critics argue this is the same wishful thinking that underlies C++ core guidelines, sanitizers, and smart pointers — incremental improvements that do not address the fundamental problem.

The honest position is: we do not know yet. The memory safety design is not finalized, let alone implemented. It is the piece of Carbon’s story most likely to change significantly before any 1.0 release.


The Honest Reality Check: Will Carbon Replace C++?

This is the question everyone wants answered, so let’s answer it honestly.

Arguments For Carbon’s Success

Google’s investment is real. This is not a side project. Carbon has a dedicated engineering team at Google. Chandler Carruth, who runs LLVM at Google, is the lead. Google has enormous incentives to solve the C++ succession problem — they have hundreds of millions of lines of C++ that they cannot realistically rewrite in Rust, and they need a path to better safety and ergonomics. They have already invested years and substantial engineering effort into Carbon.

The C++ interop story is genuinely unique. No other language does what Carbon is attempting with C++ interop. Rust’s C++ interop (via cxx, autocxx, or bindgen) requires explicit binding layers. Zig has C interop but not C++ interop at the same depth. If Carbon’s C++ interop actually works as designed, it removes the biggest barrier to language adoption for large C++ codebases.

The design is technically sound. Checked generics, the package system, the explicit class hierarchy, the var/let distinction — these are all improvements over C++ that fix real problems without introducing new ones. The language is thoughtfully designed by people who have spent careers in C++ and understand its real problems.

The existing C++ ecosystem wins. Carbon can use the entire C++ ecosystem as-is. All of LLVM, Boost, Abseil, protobuf, gRPC — accessible from Carbon without rewriting anything. This is a huge advantage over Rust, which requires rewriting or wrapping these libraries.

Arguments Against Carbon’s Success

C++ inertia is enormous. C++ is not just a language — it is a 40-year ecosystem of build systems, debuggers, sanitizers, profilers, IDEs, textbooks, experts, and interview questions. The ecosystem switching cost is not zero for individuals but is staggering for organizations. C++ engineers exist in massive numbers. Carbon engineers do not exist yet.

Rust is already winning the “safer systems language” niche. The Linux kernel, Android components, Windows components, ChromeOS drivers, Cloudflare’s infrastructure, Discord’s backend, 1Password — Rust is already in production in major systems. It has a rich ecosystem, a growing community, and is filling exactly the niche that Carbon wants to fill. Carbon is arriving late to a space that Rust is already occupying.

The C++ committee is actually improving the language. C++20 brought Concepts (better generics), Ranges, Coroutines, and Modules. C++23 added more range improvements, std::expected, stackful coroutines, and more. C++26 will include reflection, pattern matching, and better concurrency primitives. The language is not static. Every year that passes narrows the gap between “modern C++” and “what Carbon offers.”

Compiler complexity is severely underestimated. Implementing full, production-quality C++ interop is one of the hardest problems in compiler engineering. Clang took a decade to mature. C++ has undefined behavior, complex name resolution, SFINAE, ADL, initializer list edge cases, and ABI considerations that have surprised even expert compiler engineers. Carbon is attempting to implement interop with this while simultaneously building a new language. This is an extraordinary amount of work for a relatively small team.

Pre-0.1 in 2025/2026 is concerning. The project was announced in 2022. After nearly four years of development, it is still pre-0.1. This suggests the implementation challenges are significant. Languages that take this long to reach 1.0 tend to run into project fatigue, especially if they are dependent on a single company’s continued investment.

The Realistic Scenario

The most realistic scenario for Carbon, given the current trajectory, is something like:

Best case: Carbon reaches a stable 1.0 around 2027-2028. Google adopts it internally for new C++ projects. A handful of other large C++ shops experiment with it. It becomes a niche but respected option for C++ engineers who want better ergonomics without abandoning their existing codebase. It influences the C++ standards committee (this is already happening — some Carbon ideas are finding their way into C++ proposals).

Most likely case: Carbon matures slowly, remains largely a Google-internal project, and serves as a proof-of-concept for what C++ interoperability could look like. Its most lasting contribution is to the discourse around language design and interoperability, and possibly to C++ standards improvements.

Worst case: Carbon fails to reach critical mass, the team shrinks, and the project gradually winds down — joining the graveyard of C++ successors like D, Vala, and Nim (which survive but never achieved mainstream adoption).

Who should be watching Carbon right now?

  • C++ engineers at large companies with significant technical debt who are interested in safer alternatives
  • Language designers and compiler engineers interested in C++ interop techniques
  • Engineers who find Rust’s borrow checker too demanding for their use case
  • Google employees, who may find Carbon adoption mandated internally before external adoption picks up

Comparison to Alternatives

Carbon exists in a crowded space. Understanding where it fits requires comparing it to the realistic alternatives.

Carbon vs Rust

Carbon and Rust have different goals that are worth stating clearly:

Rust is a new systems programming language that happens to be able to call C code (via FFI). It was designed from scratch with memory safety as the primary goal. It does not prioritize backward compatibility with C++. If you write new systems code today and memory safety is the priority, Rust is the right choice. Rust is production-ready, battle-tested, and has a rich ecosystem.

Carbon is a C++ successor designed to interoperate with existing C++ codebases. It prioritizes incremental migration from C++ over radical new design. If you have an existing multi-million-line C++ codebase that you cannot realistically rewrite, Carbon is the bet that may eventually make more sense than Rust.

These are not competing — they are targeting different problems. A Rust evangelist and a Carbon advocate are not arguing about the same thing. The interesting question is whether Carbon reaches enough maturity to be a real option before the teams that would use it give up and choose Rust instead.

Carbon vs Cpp2 / Cppfront

Herb Sutter (ISO C++ committee chair, author of the C++ Core Guidelines, principal at Microsoft) has proposed Cpp2 — a syntactic reform of C++ that compiles to standard C++. The tool cppfront translates Cpp2 syntax to C++23.

The philosophy is fundamentally different from Carbon:

  • Cpp2/cppfront compiles to C++, not to machine code via LLVM
  • It is a strict superset — any valid C++ is valid Cpp2
  • It aims to make “safe C++” the easy path through syntax improvements
  • It can be used today, with standard C++ toolchains

Carbon takes a more radical approach — it is a new language, not a syntactic reformulation of C++. Carbon can make deeper changes (removing undefined behavior from signed overflow, cleaning up the type system) that Cpp2 cannot because Cpp2 must remain valid C++ after translation.

Which is right? Cpp2’s conservatism makes it more likely to be incrementally adopted by individual developers today. Carbon’s boldness might produce a better language eventually, but requires more investment. Both are interesting experiments. Neither is production-ready in the same sense as shipping software.

Carbon vs Circle

Circle is a C++ compiler extension (not a separate language) that adds various features to C++: pattern matching, improved generics, optional safety features, and more. It is a production compiler that extends Clang. Sean Baxter, its creator, argues that the right approach is to extend C++ rather than replace it.

Circle is genuinely impressive and production-usable today. However, it is primarily one engineer’s project, and extensions that are not in the C++ standard create portability and maintenance concerns. Carbon has a team and a design process; Circle has one extraordinarily talented engineer.

Carbon vs Zig

Zig is a systems programming language designed to replace C, not C++. It has no hidden control flow, comptime (compile-time execution), and excellent C interop. Zig does not have classes, inheritance, or the kind of C++ interop that Carbon targets.

Zig is production-ready (or nearly so, as of 0.12+) and has a passionate community. If you want to replace C (not C++) and value simplicity and comptime as a metaprogramming model, Zig is worth serious consideration. Carbon and Zig are targeting different problems.

Carbon vs Swift (for Systems Work)

Swift has been experimenting with low-level systems programming features (Swift for Systems, Embedded Swift). It has an excellent type system, value types, and optionals. However, Swift’s C++ interop, while improving, is not as deep as what Carbon intends. Swift is primarily an Apple ecosystem language, which limits its reach for cross-platform systems work.

Summary Comparison Table

Language Production Ready C++ Interop Memory Safety Primary Use
Rust Yes C only (FFI) Borrow checker New systems code
Zig Nearly C only Manual + safety modes C replacement
Carbon No (pre-0.1) Deep (designed) Planned C++ migration
Cpp2 Experimental Full (is C++) Partial C++ reform
Circle Yes (extension) Full (is C++) Opt-in C++ extension
D Yes Good GC + @safe C++ alternative
Swift Yes Partial C++ Optionals + ARC Apple ecosystem

Experimenting with Carbon Today

Despite the pre-production status, you can experiment with Carbon today. Here is how.

Building the Carbon Toolchain

The Carbon repository lives at github.com/carbon-language/carbon-lang. The build system uses Bazel.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Prerequisites: Clang, LLVM, Bazel, Python 3
# On Ubuntu/Debian:
sudo apt install clang llvm lld python3 python3-pip

# Install Bazelisk (Bazel version manager)
npm install -g @bazel/bazelisk
# Or:
curl -Lo bazelisk https://github.com/bazelbuild/bazelisk/releases/latest/download/bazelisk-linux-amd64
chmod +x bazelisk && sudo mv bazelisk /usr/local/bin/bazel

# Clone the repo
git clone https://github.com/carbon-language/carbon-lang
cd carbon-lang

# Build the Carbon Explorer
bazel build //explorer:explorer

# Build the Carbon Toolchain (compiler)
bazel build //toolchain:carbon

# Run tests to verify the build
bazel test //explorer/...

The build process can take 30-60 minutes on the first run, as it builds LLVM from source. Subsequent builds are incremental.

Running Carbon Programs with the Explorer

The Carbon Explorer executes Carbon code as an interpreter. It is useful for experimenting with language semantics:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# Create a simple Carbon program
cat > hello.carbon << 'EOF'
package Sample api;

fn Main() -> i32 {
  Print("Hello, Carbon!");
  return 0;
}
EOF

# Run it with the Explorer
./bazel-bin/explorer/explorer hello.carbon

A More Complete Example

Here is a program that demonstrates several Carbon features together, based on examples from the Carbon repository:

package Sample api;

// An interface for types that can be compared
interface Comparable {
  fn LessThan[self: Self](other: Self) -> bool;
}

// A generic function that finds the minimum using the interface
fn Min[T:! Comparable](a: T, b: T) -> T {
  if (a.LessThan(b)) {
    return a;
  }
  return b;
}

// A simple class
class Temperature {
  var celsius: f64;

  fn FromCelsius(c: f64) -> Temperature {
    return {.celsius = c};
  }

  fn FromFahrenheit(f: f64) -> Temperature {
    return {.celsius = (f - 32.0) * 5.0 / 9.0};
  }

  fn ToFahrenheit[self: Self]() -> f64 {
    return self.celsius * 9.0 / 5.0 + 32.0;
  }
}

// Implement Comparable for Temperature
impl Temperature as Comparable {
  fn LessThan[self: Self](other: Temperature) -> bool {
    return self.celsius < other.celsius;
  }
}

fn Main() -> i32 {
  let boiling = Temperature.FromCelsius(100.0);
  let freezing = Temperature.FromCelsius(0.0);
  let body_temp = Temperature.FromFahrenheit(98.6);

  let coldest = Min(boiling, freezing);
  Print("Coldest: {0} celsius", coldest.celsius);

  let warmer = Min(boiling, body_temp);
  Print("Warmer: {0} fahrenheit", warmer.ToFahrenheit());

  return 0;
}

The Online Playground

As of 2025/2026, there is no fully-featured online Carbon playground comparable to the Rust Playground or Go Playground. The Carbon repository has some web-based demos, but for serious experimentation you need to build the toolchain locally.

Reading the Specification

The Carbon language specification is in the repository at docs/design/. It is written in plain Markdown and is remarkably readable. If you want to understand what Carbon is designed to do:

1
2
3
4
5
6
7
8
9
# Key design documents
ls carbon-lang/docs/design/

# The generics design is particularly detailed:
# carbon-lang/docs/design/generics/overview.md
# carbon-lang/docs/design/generics/details.md

# The interop design:
# carbon-lang/docs/design/interoperability/README.md

The generics documentation in particular is excellent — it explains the design decisions in depth and is worth reading even if you never write a line of Carbon, just for the insight into programming language design trade-offs.


What Carbon Gets Right That C++ Gets Wrong

It is worth being explicit about the technical improvements in Carbon’s design, independent of whether Carbon succeeds as a language.

No undefined behavior from signed integer overflow. In C++, signed integer overflow is undefined behavior — the optimizer can assume it never happens, leading to surprising transformations and security vulnerabilities. In Carbon, signed integers are defined to wrap (two’s complement behavior). This matches what every CPU does, what engineers expect, and eliminates an entire class of security bugs.

No implicit conversions. C++’s implicit conversion rules are a source of bugs and surprises. int to long, int to float, bool to int, pointer to bool — these happen silently. Carbon requires explicit conversions, which makes code clearer and prevents accidental precision loss or logic errors.

No most-vexing-parse. The ambiguity in C++ that makes Type t(Type2()) parse as a function declaration rather than an object construction is legendary. Carbon’s grammar is unambiguous by design.

No preprocessor. Carbon has no #define, no #include, no #ifdef. The package system replaces headers. Conditional compilation uses different mechanisms. This makes tooling, IDE support, and incremental compilation dramatically simpler.

Package system instead of headers. C++ headers are compiled independently each time they are included, leading to slow compilation in large codebases. Carbon’s package system with explicit api declarations enables proper modular compilation.

Consistent and readable syntax. Carbon’s syntax is explicitly designed for human readability and unambiguous machine parsing. There are no cases where the same syntax means different things depending on context.

Checked generics with interface constraints. As covered in depth above — this is a fundamental improvement over C++ templates that benefits error messages, tooling, documentation, and separate compilation.


What Carbon Still Needs to Figure Out

In fairness, several important problems are not yet solved in Carbon:

Error handling. Carbon does not have a finalized error handling strategy. The choice type for result types exists, but the language does not have exceptions (intentionally), and the ergonomics of error propagation are still being designed. C++ has std::expected now, and Rust has ? for propagating Result — Carbon needs something equally clean.

Build system integration. Carbon uses Bazel. Most C++ codebases use CMake or Meson. Getting Carbon to work smoothly with existing build systems is important for adoption and is not yet figured out.

Package management. Carbon does not have a package manager. Rust has Cargo; Go has modules. Carbon’s package system is designed, but how you would depend on third-party Carbon libraries is unclear.

Debugging and tooling. DWARF debug info, GDB/LLDB support, sanitizers (AddressSanitizer, UBSan), profilers — these need to work with Carbon for it to be usable in production. Some of this is inherited from LLVM, but Carbon-specific tooling is thin.

Standard library. Carbon’s standard library is in early stages. You cannot write serious programs in Carbon without implementing basic data structures yourself, or relying on the C++ stdlib via interop.

The memory safety design. As discussed — the most important piece of Carbon’s value proposition for future safety is the least developed part of the design.


Key Takeaways for Systems Programmers

  1. Carbon is not production ready. Full stop. If you need to ship software, use C++, Rust, or another mature language. Carbon is an experiment.

  2. The C++ interop story is uniquely compelling. Nothing else offers what Carbon is attempting. If it works, it genuinely changes the migration calculus for large C++ codebases.

  3. Checked generics are a real improvement. The interface/impl system and compile-time constraint checking are better than C++ templates in ways that matter for large codebases. This design is worth studying regardless of Carbon’s fate.

  4. The memory safety story is incomplete. Do not choose Carbon over Rust because of memory safety promises. Rust delivers memory safety today. Carbon’s safety design is still on the drawing board.

  5. Watch it, but do not bet on it. Follow Carbon’s development. Read the specification. Experiment with the Explorer. But do not make technology decisions based on where Carbon is going — make them based on where it is.

  6. Carbon’s influence may outlast Carbon itself. Even if Carbon never achieves mainstream adoption, its ideas about interoperability, checked generics, and C++ succession are influencing language design discussions broadly. The Cpp2 and C++26 design discussions are happening in a context shaped partly by Carbon.


Conclusion

Carbon is a serious, technically sophisticated attempt to solve a genuinely hard problem: how to modernize C++ without abandoning the enormous investment the industry has made in C++ codebases. The C++ interop story is unique and important. The checked generics design is a real improvement. The team knows what they are doing.

But honesty requires acknowledging the gap between Carbon’s current state and Carbon’s vision. Pre-0.1 after nearly four years is not a confident trajectory. The memory safety design is more roadmap than reality. The standard library is thin. Build system integration is unsolved. And Rust, which solves many of the same problems with a different philosophy, is already in production in major systems.

The Kotlin-for-Java analogy is apt in more ways than one. Kotlin succeeded because JetBrains was fully committed, because Google adopted it for Android (providing a massive captive audience), and because the Java ecosystem needed Kotlin — Java had not kept up with language development. Carbon succeeds only if Google is comparably committed, if a large ecosystem needs a C++ successor badly enough to invest in it, and if C++ does not improve fast enough to narrow the gap.

Whether those conditions hold, the next few years will tell.

In the meantime, if you write C++ for a living: understand Carbon, follow its development, and be ready to evaluate it seriously when (and if) it reaches stability. If you are starting a new project today that needs systems-level performance and memory safety, use Rust. If you have an existing C++ codebase and want incremental improvement, follow Carbon’s interop story closely — it may eventually be exactly what you need.

The most interesting version of Carbon’s future is not “Carbon replaces C++” but “Carbon provides the migration path that lets large C++ codebases gradually become safer without a rewrite.” That would be a significant contribution even if Carbon itself remains a niche language. That future is possible. It is not yet probable.


Further Reading

Comments