Carbon Copy No.9: Generics Part I

5 views
Skip to first unread message

Daniel 'Wolff' Dobson

unread,
Nov 18, 2025, 11:30:13 PMNov 18
to anno...@carbon-lang.dev

Carbon Copy, November 2025

Here is the new Carbon Copy, your periodic update on the Carbon language!

Carbon Copy is designed for those people who want a high-level view of what's happening on the project. If you'd like to subscribe, you can join anno...@carbon-lang.dev. Carbon Copy should arrive roughly (sometimes extremely roughly) every other month.

Toolchain update

Work on the toolchain continues. Here's a recent example of complex interop:


// Import a regular C++ library.

import Cpp library "re2/re2.h";


// `inline` is useful for short snippets of C++.

import Cpp inline #'''c++

#include <string_view>

#include <cstdio>

void Log(std::string_view sv) {

  std::printf("%.*s\n", (int)sv.size(), sv.data());

}

'''#;


class Regexp {

  // Extend a Carbon class from a C++ base class.

  extend base: Cpp.re2.RE2;


  // We define an idiomatic `Make` Carbon factory function to create

  // instances of `Regexp`.

  fn Make(re: str) -> Self {

    // * The name `RE2` isn't found in `Regexp`, so Carbon searches the base

    //   class. Because that is a C++ class, C++ name lookup is used, which

    //   finds the RE2 constructor.

    // * The RE2 constructor is called to initialize the `RE2` base class

    //   subobject of the Carbon `Regexp` instance.

    // * `re` is a `str`, which is a Carbon class, but it gets translated on

    //   the interop boundary to C++'s `std::string_view` class.

    // * The `RE2` constructor is overloaded; it uses Clang's overload

    //   resolution to pick which constructor to call.

    return {.base = RE2(re)};

  }


  fn Submatch[addr self: Self*](text: str) -> str {

    var submatch: str;

    // * `*self as Cpp.re2.RE2` upcasts `*self` to the C++ `RE2` base class

    //   of the Carbon `Regexp` derived class.

    // * `FullMatch` is a C++ variadic template found from `RE2`, so this is doing

    //   template argument deduction, instantiation, and all of the related C++

    //   machinery from a Carbon call.

    if (FullMatch(text, *self as Cpp.re2.RE2, &submatch)) {

      return submatch;

    }


    return "Match failed";

  }

}


fn Run() {

  // Create an instance of the Carbon `Regexp` class with a C++ base class.

  var re: Regexp = Regexp.Make(".* (.*)!");

  // Calls the `Log` function we defined in the `Cpp inline` block to

  // output "Carbon".

  Cpp.Log(re.Submatch("Hello, Carbon!"));

}


Under the covers this is building a Clang AST from these C++ sources, then pulls what's needed out of the AST into the Cpp package in Carbon. Note that the C++ constructor RE2() is overloaded, so Clang is choosing which implementation to call. 


And finally, since FullMatch is a variadic template, all of the C++ machinery gets called in the context of Carbon including template argument deduction and instantiation.


You can play with a more complete example by running bazel build -c opt //examples/re2_playground in a recent Carbon checkout.

Memory safety update

We are developing a memory safety model that allows significantly more incremental migration for existing C++ code without rearchitecture, in particular models that allow mutable aliasing. We first looked at a Rust-like type capability model, with shared mutability inspired by Ante, but our current focus is researching approaches that annotate functions with effects. This has been inspired by recent work; see:

The advantage of this approach is that it appears to allow more flexibility to prove more code pattern-safe, including many code patterns commonly used in C++. However, this comes at the cost of increased complexity when specifying a function's contract. Our current work is collected in our memory safety drive folder, where we are tackling individual topics in a sequence of "safety units."

Spotlight: Generics Part I

In this Carbon Spotlight, we're taking a first look at generics. Carbon takes two major approaches to generics: checked generics, which is the modern idiomatic core approach, and template generics, which are designed mainly for C++ compatibility and migration. Today we'll discuss checked generics.


Checked generics provide three major features:


First, they provide compilation speed. The compiler only needs to check the body of a generic function once, when it is defined. This stands in contrast to C++ templates, which require checks and rechecks during compilation for every instantiation.


Second, they give early and clear errors: Early type checking directly leads to clear errors. Instead of late-binding errors resulting in long, confusing template diagnostics, users receive error messages that plainly show which requirements are not met. We strive to prevent monomorphization errors entirely, although they remain possible in rare edge cases.


Finally, checked generics use nominal constraints: Carbon types satisfy criteria by explicitly declaring what interfaces they implement ("nominally"), rather than (perhaps coincidentally) having functions with specific names and parameters ("structurally"). 


Let's take a look, then, at how Carbon handles polymorphism through interfaces and checked generic functions. 

Carl Linnaeus would be proud

Carbon only supports single inheritance. In an animal class hierarchy, we might have Mammal and Insect base classes which define features like number of legs. All children of Insect should have six legs, while all children of Mammal produce milk[1].


However, some subclasses share a characteristic like fur. We want to have predictable behavior for handling hair, but we can't make HairyAnimal a base class of Insect, because although bees are furry, ants are not[2]. For checked generics, we want the compiler to be sure brushing a given object is supported before allowing it.


So, let's get started.


import Cpp library "stdio.h";


// Utility function

fn Print(msg: array(Core.Char, 6)) {

  for (n: Core.Char in msg) {

    Cpp.putchar((n as u8) as i32);

  }

}


// Interface definition (Nominal constraint)

interface Hairy {

  fn Brush[self: Self]();

}


Classes that implement the Hairy interface can be Brushed. Interfaces function as the only static open extension mechanism in Carbon. This means that the capabilities required by a generic function are clearly encapsulated in the interface constraints, making type checking possible without viewing all potential implementations.


Let's now define a checked generic function for Groom, and specialize it for various pets. The [...] covers implicit parameters, which are arguments the caller doesn't explicitly pass in `(`...`)`. (As covered in our last newsletterself is a kind of implicit parameter.)


// Checked generic function definition

fn Groom[T:! Hairy](pet: T) {

  // Compiler checks the definition here based on the Hairy interface.

  pet.Brush();

}


:! marks a checked compile-time parameter, in contrast to runtime parameters marked with :.


If there were a typo here, the error is immediate (in contrast to C++).


// Checked generic function definition

fn Groom[T:! Hairy](pet: T) {

  // ❌ Fails at this point in file during compilation

  // `error: member name `Bruh` not found in `Hairy``.

  pet.Bruh(); 

}


We need a class to implement the interface, so let's pick a cat as they are pretty brushable.[3]


class Cat { }

impl Cat as Hairy {

  fn Brush[self: Self]() {

    Print(('P', 'u', 'r', 'r', '!', '\n'));

  }

}


The impl line indicates that Cat is Hairy; that is, it will fulfill the promise made by Hairy with a Brush implementation. If fn Brush weren't defined, that would be a compile error at that location.


We can add a bee, too, as bees need to look good, but let's also add a duck that can be brushed, but isn't hairy.


class Bee {

  // impl Hairy inside the class definition for variety

  impl as Hairy {

    fn Brush[self: Self]() {

      Print(('B', 'u', 'z', 'z', '!', '\n'));

    }

  }

} 


// Add a duck-typed Duck

class Duck {

  fn Brush[self: Self]() {

    Print(('Q', 'u', 'a', 'c', 'k', '\n'));

  }

}


Now let's try it out:


fn Run() {

  let bella: Cat = {};

  // ✅ Caller satisfies the interface requirement.

  Groom(bella);

  

  let barry: Bee = {};

  // ✅ Caller satisfies the interface requirement.

  Groom(barry);


  let della: Duck = {};

  // Prints "quack".

  della.Brush();

  // ❌ Does not compile because you can brush a Duck, but it isn't Hairy.

  Groom(della); 

}

Interfaces like Hairy provide static open extension, allowing Cat and Bee to define their own implementations of the methods specified by the interface, decoupling the caller (Groom) from the implementations for each type. And, it's a nominal Interface, since duck-typed Duck can't brush because it doesn't explicitly implement the behavior.


See the Compiler Explorer version that runs, and the one that shows errors.

Further with generics

Generics serve as a primary mechanism for achieving many critical architectural functions within Carbon. They replace what other languages would accomplish using multiple inheritance, as shown here, but they are also the core mechanism for overriding operators. 


While checked generics are the preferred mechanism for idiomatic Carbon code, we maintain templates for dealing with C++ interoperation and providing a migration path. Templates follow a duck-typing approach, allowing incremental migration to the more rigorous checked generics system.


Coming up soon in Spotlight: More generics! We've barely scratched the surface. If you have questions or specific interests, don't hesitate to follow up with us on Discord.


More to read:

  • Carbon has developed an invented solution to handle variadics specifically within the checked generics framework. See Geoffrey Romer's talk from 2024 for an overview.

Carbon out and about

Recent proposals and issues

If you want to keep up on Carbon’s proposals and issues, follow "Last Week In Carbon". Check out the RSS feed!

Recently accepted proposals including:



Recently closed leads issues including:

Wrap-up

Don't forget to subscribe! You can join anno...@carbon-lang.dev. If you have comments or would like to contribute to future editions of Carbon Copy, please reach out. And, always, join us any way you can!


Yours in hoping we can we can go on a hike,
Wolff, Josh, Richard, and the Carbon team


[1] Some insects produce "milk", too, but we digress.
[2] But what of the velvet ant? It's a wasp.
[3] But wait! I hear you cry. What of a Sphynx cat? True, they aren't as brushable as, say, a fuzzy Scottish Fold, but Sphynx cats are covered in a downy hair that you could, in fact, brush. Technically correct is the best kind of correct.
Reply all
Reply to author
Forward
0 new messages