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.
As this is the last Carbon Copy of the year, we thought it would be interesting to look back on the toolchain's progress in the past 12 months. Last year, we were celebrating the ability to solve Advent of Code problems in Carbon. This year, let's see how much better we can solve those same problems, by looking at the very first one. Here's what we had a year ago:
import Core library "io";
import library "day1_common";
import library "sort";
fn Abs(n: i32) -> i32 { return if n < 0 then -n else n; }
fn Run() {
var a: [i32; 1000];
var b: [i32; 1000];
var n: i32 = ReadInputs(&a, &b);
Quicksort(&a, 0, n);
Quicksort(&b, 0, n);
var i: i32 = 0;
var difference: i32 = 0;
while (i < n) {
difference += Abs(a[i] - b[i]);
i += 1;
}
Core.Print(difference);
}
And here's what we have now. On the surface, it may appear that not much has changed, but digging deeper, there are major differences reflecting significant advances in the toolchain.
import Core library "io";
import Core library "range";
import Cpp library "<cstdlib>";
import library "day1_common";
import library "sort";
fn Run() {
var a: array(i32, 1000);
var b: array(i32, 1000);
// The change from passing `&a` to passing `ref a` represents a
// major overhaul not only in the toolchain, where this is
// using the newly-added pattern matching infrastructure, but
// also in the language design. Pass-by-reference is one of the
// first visible changes of the introduction of "forms" to
// Carbon's type system.
let n: i32 = ReadInputs(ref a, ref b);
// A year ago, `Quicksort` was a special-case function that
// only supported arrays of exactly 1000 `i32`s. Today's
// `Quicksort` is a generic function that operates on any
// number of elements of any comparable type.
Quicksort(ref a, 0, n);
Quicksort(ref b, 0, n);
var difference: i32 = 0;
// We previously didn't have a `for` loop, and faked one up
// with a `while` loop. Today's implementation uses a `for`
// loop, which operates generically over arbitrary iterable
// types. `Core.Range` is a pure Carbon library implementation
// of a range of sequential integers 0..n-1.
for (i: i32 in Core.Range(n)) {
// This version uses `std::abs` from the C++ standard library
// instead of defining its own `Abs` function. This is a tiny
// corner of the huge strides we've made in supporting
// friction-free C++ interoperability in the past year –
// notably, `std::abs` is a function template that's part of
// an overload set, and we're selecting the appropriate
// overload and performing template argument deduction,
// instantiation, and code generation to make this call work.
difference += Cpp.std.abs(a[i] - b[i]);
}
Core.Print(difference);
}
In today's spotlight, more generics! Generics are Carbon's way of providing extensibility and abstraction to the language. As with most features in Carbon, generics are designed to be consistent, readable, and made of repeating patterns.
There are two major systems, (type-)checked generics and templates. This Spotlight, as with the previous one, focuses on checked generics, which are Carbon-idiomatic. Checked generics are better than templates for compilation speed and early error messages that point clearly at the problem.
Last time, we learned about checked generics through implementing an interface. We'll now cover two variations on that theme:
impl vs extend impl
associated constants and where clauses
This is a running code example, so let's start with some imports.
// Note that the use of PrintStr below
// is not *quite* supported yet, but will be soon.
import Core library "io";
In our previous Carbon Copy spotlight, we made impl Cat as Hairy to give a Cat the ability to be Groom()ed. Groom was an interface. In this case, let's imagine something a little different with some possible name collisions.
interface Scary {
fn Boo[self: Self]();
}
We need a ghost to be Scary.
class Ghost {
// `extend impl` extends the API for this class.
extend impl as Scary {
// Booing is pretty fundamental to a ghost.
fn Boo[self: Self]() {
PrintStr("oooOOOoooOOOooo\n");
}
}
}
fn Run() {
let ghost: Ghost = {};
// Here, within the namespace of `Ghost`, there is `Boo`.
// Prints out an eerie howl.
ghost.Boo();
}
Imagine another interface and an implementation, in the same style as above:
interface Rowdy {
fn Boo[self: Self]();
}
class AudienceMember {
extend impl as Rowdy {
fn Boo[self: Self]() {
PrintStr("Get better material!\n");
}
}
}
In both cases above, we've used extend impl, which adds Boo to the base's namespace. We can choose not extend the API by dropping extend from impl:
class NonExtendedAudienceMember {
// impl does *not* extend NonExtendedAudienceMember's API.
impl as Rowdy {
fn Boo[self: Self]() {
PrintStr("Get better material!\n")
}
}
}
fn Run() {
let neam: NonExtendedAudienceMember = {};
// ❌ Boo is not in NEAM's namespace.
neam.Boo();
// But, it *is* in Rowdy's namespace.
neam.(Rowdy.Boo)();
}
But what of a haunted comedy club?
class HauntedAudienceMember {
extend impl as Rowdy {
fn Boo[self: Self]() {
PrintStr("Meh!\n");
}
}
extend impl as Scary {
fn Boo[self: Self]() {
PrintStr("A scary boo!\n");
}
}
}
When we try to Boo as we had above, we will have to be specific about what kind of booing we want:
fn Run2() {
// Remember a struct will be promoted to a class instance
let mean_ghost: HauntedAudienceMember = {};
// The HauntedAudienceMember namespace has a collision.
// ❌ error: "ambiguous use of name `Boo` found in multiple
// extended scopes"
mean_ghost.Boo();
// Get around by qualifying which Boo you mean
mean_ghost.(Scary.Boo)();
}
If you always meant to be scary, you can use an alias to pick that one.
fn Run3 () {
// Add this line to HauntedAudienceMember:
// alias Boo = Scary.Boo;
// This will map Boo within HAM's namespace to Scary.Boo
let mean_ghost: HauntedAudienceMember = {};
mean_ghost.Boo();
}
Lastly, you are allowed to shadow an implemented identifier:
class HauntedAudienceMember {
// ...
// extend impl `Rowdy` and `Scary` as above
// ...
fn Boo[self: Self]() {
self.(Scary.Boo)();
self.(Rowdy.Boo)();
}
}
Bottom line: You should use extend impl when you want to include the API in your own, and impl when ambiguity would make for confusing reading.
This example code is available at https://godbolt.org/z/dnr8qjnxc.
A bear wants honey, but won't get honey from hornets[1]. We'd like our bear to be able to open up a hive, but leave a hornet's nest alone. Let's set up some classes.
class Honey {
fn Eat[self: Self]() {
PrintStr("Yum!");
}
}
Now, bears open a lot of things: hives, clams, trash cans, and so on. Let's make a generic container interface that knows about being opened, and opens to contain specific things.
interface Container {
// An associated constant type that implementing classes must define.
let Content:! type;
fn Open[self: Self]() -> Content;
}
The type Content is constant for any particular implementation of this interface. The type is associated with Container in its particular impl block.
We can see that below with a where clause:
class Hive {
extend impl as Container {
// We specify the associated constant in the impl definition.
// All associated entities without defaults must be specified
// in an impl.
// Note this placement of `where` is allowed by the language design ,
// but not implemented yet. In the current toolchain, write
// `extend impl as Container where .Content = Honey {`.
where .Content = Honey;
// Open is also an associated entity that we define here.
fn Open[self: Self]() -> Honey {
return {};
}
}
}
Now, let's open the hornet's nest:
class AngryCollectionOfHornets {}
class Nest {
impl as Container {
// We specify the associated constant in the impl definition.
where .Content = AngryCollectionOfHornets;
fn Open[self: Self]() -> AngryCollectionOfHornets {
return {};
}
}
// `Open` defined outside the Container namespace; this is OK.
fn Open[self: Self]() {
PrintStr("A different kind of open");
}
}
If bears comes across a Hive, that's great, but more generally we want them to be able to open anything that contains Honey:
// A generic function that accepts any Container that
// contains Honey.
fn InspectGoodies[T:! Container where .Content = Honey](c: T) {
// Because of the `where` clause, the compiler knows Open()
// returns Honey. It looks up the namespace of Container, not
// whatever the actual type being passed in
var treat: Honey = c.Open();
treat.Eat();
}
fn Run() {
var lunch: Hive = {};
// OK: Hive.Content is Honey
InspectGoodies(lunch);
var nest: Nest = {};
// ❌ Fails at compile time, since Containter.Content isn't Honey
// "error: cannot convert type `Nest` into type implementing
// `Container where .(Container.Content) = Honey`"
InspectGoodies(nest);
}
This code example is available in https://godbolt.org/z/odnovxnba.
It's worth contrasting this to C++. InspectGoodies works on "Container.Content" whether or not the target type has it in its namespace (e.g. extend impls Container instead of just impling it). In C++ templates, it would always look in the namespace of the type of whatever object was passed in. Also, since Carbon is nominal, we can type check early, and bind to the right Open right away, or else fail right right away.
Is this kind of like Rust? A bit, although where goes in a different place. In Carbon, the where keyword acts as a binary operator within type expressions, often inline within parameter lists like [T:! Container where .Content = Honey], and when introducing a declaration in an impl. In Rust checked generics, where clauses appear as bounds right before the { in a definition.
Use extend impl when you want to extend the API, and use associated constraints to make sure you're using
Use where to select types inline with type definitions
Do not try to open hornets' nests looking for honey.
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:
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!
This is our last newsletter of 2025. And happy holidays, whenever your holidays are, and we’ll see you next year!
See you at the airport,
Wolff, Josh, Richard, and the Carbon team
-------
[1] Wait, I hear you cry, can't some wasps make honey? And aren't hornets wasps? Yes, but the wasps that can make honey are not hornets. The real question is whether hornets are yellow with black stripes, or black with yellow stripes.