Carbon Copy, September 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 announce@carbon-lang.dev. Carbon Copy should arrive roughly (sometimes extremely roughly) every other month.
Toolchain update
Work on the toolchain continues. We have made strides in interop!
If you just want to say hello:
import Cpp library "stdio.h";
fn Run() {
let hello: array(Core.Char, 6) = ('H', 'e', 'l', 'l', 'o', '!');
for (n: Core.Char in hello) {
Cpp.putchar((n as u8) as i32);
}
}
For more complicated interop, say we have the following `circl…
Carbon Copy, September 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 announce@carbon-lang.dev. Carbon Copy should arrive roughly (sometimes extremely roughly) every other month.
Toolchain update
Work on the toolchain continues. We have made strides in interop!
If you just want to say hello:
import Cpp library "stdio.h";
fn Run() {
let hello: array(Core.Char, 6) = ('H', 'e', 'l', 'l', 'o', '!');
for (n: Core.Char in hello) {
Cpp.putchar((n as u8) as i32);
}
}
For more complicated interop, say we have the following circle.h
:
// square.h
struct Point {
int x, y;
};
class Circle {
public:
Point center;
int radius;
Circle() : center{42, 0} {};
};
The matching Carbon code:
import Cpp library "circle.h";
import Core library "io";
fn Run() -> i32 {
var circle: Cpp.Circle = Cpp.Circle.Circle();
Core.Print(circle.center.x);
return 0;
}
You can try it yourself at the Compiler Explorer!
Spotlight: Classes Part II
In our last Carbon Copy Spotlight we talked a lot about classes. (If you haven’t read it, start there!)
As we showed last time, you can initialize an object using a struct:
var polygon: Polygon = {.sides = 8};
When Carbon initializes an object, it uses the fields in the struct literal to initialize fields in the class instance created on the left-hand side. This allows us to move away from specialized C++ constructor syntax and complex behavior, and move instead to using factory functions.
Factory functions are ordinary functions that can live as class functions on the class type or separately. Consider a solar system database, where we track various celestial objects. Here’s how you could define a Planet
class and a factory function:
class Planet {
// Every planet has a diameter; this is in 100,000kms
var diameter: i32;
// A factory function to create an Planet instance
// Note: No [ref Self] in the definition;
// it's a class function
fn Make(diameter: i32) -> Planet {
// Stores diameter in the Planet passed in for the
// return value; avoids copies.
return {.diameter = diameter};
}
}
fn Main() {
var planet: Planet = Planet.Make(50);
}
Carbon aims for simplicity by avoiding special constructor rules, initializer lists, or separate rules for error handling during construction. Instead, it uses consistent function syntax and explicit initialization patterns that are familiar across the entire language.
Now, imagine we make Planet
abstract, and make a non-abstract subclass called RingPlanet
(you know, Saturn, Uranus, etc.).
// This is abstract only because we use the keyword;
// there are no unimplemented methods.
abstract class Planet {
var diameter: i32;
}
class RingPlanet {
extend base: Planet;
var ring_count: i32;
}
fn Main() {
var saturn: RingPlanet =
{.base = {.diameter = 110}, .ring_count = 7};
}
As class hierarchies get deeper, each superclass will need its own .base
and set of curly braces in the initialization, and that will get cumbersome. We could make factory functions for the base classes to simplify, but we have a problem:
abstract class Planet {
var diameter: i32;
// ❌ I can't return an abstract Planet
fn Make(diameter: i32) -> Planet { return {.diameter = diameter}}
}
What can we do?
Partial Classes: Building Blocks for Inheritance
Partial classes are a special construct used when working with inheritance. It represents the base class portion of an object that is not yet fully constructed.
Partial classes allow code within a derived class’s construction process to initialize its base part before the fully-derived object is complete. You typically encounter partial types when implementing factory functions or C++-constructor-like logic for derived classes that extend other classes.
The partial type shares the same fields, order, and data layout as the complete base type, but it explicitly does not initialize its virtual pointer (vptr) slot. This distinction is critical because it prevents virtual method calls on an object that’s still under construction (which otherwise could lead to undefined behavior). When the derived class is fully initialized and ready, the vptr slot is filled out.
Going back to our example, let’s make a partial constructor. We’ll make diameter
private so that we couldn’t use the technique above to set its value at RingPlanet
creation time.
import Core library "io";
abstract class Planet {
// For some reason, diameter is secret
private var diameter: i32;
// This time promoted to a partially-formed Planet
fn Make(diameter: i32) -> partial Self {
// This factory function can see diameter, but the children can't
return {.diameter = diameter};
}
fn Revolve[self: Self]() {
Core.Print("Musica universalis");
}
}
class RingPlanet {
extend base: Planet;
var ring_count: i32;
fn Make(diameter: i32, rings: i32) -> RingPlanet {
// Set diameter by creating a partial Planet that has diameter
// already defined.
return {.base = Make(diameter), .ring_count = rings};
}
}
fn Main() {
var saturn: RingPlanet = RingPlanet.Make(110, 7);
// Prints message about the music of the spheres
saturn.Revolve();
}
In this example a partial Planet
represents the Planet
portion of RingPlanet
as it’s being set up, ensuring that a derived class initialization can interact with the base.
If you think all the way back to Carbon Copy No.3, the partial Planet
is part of an initializing expression. Initializing expressions require storage to be provided implicitly when evaluating the expression. In Make
, the return value of Planet
provides storage for the return value of Make
. This avoids copying, which is both economical and also allows us to create base classes that can’t be moved.
There is no reason to capture a partial Planet
outside of an initializing expression, as it cannot (at this time) be used to construct a RingPlanet
or other subclass. You cannot pass it to a function that takes a Planet
, as partial Planet
is not a Planet
. This restriction, along with the unformed vptr, means that partially-formed instances cannot be accidentally used.
fn Main() {
// Although this works, there's not much to do with a
// variable containing a partial type.
var planet: partial Planet = Planet.Make(5);
// ❌ This will fail because `partial Planet` is a
// different class than `Planet`
planet.Revolve();
}
Destroying Objects: Destructors
Every type in Carbon is destructible, meaning it has a defined destructor method that runs when the lifetime of a value of that type ends, such as when a variable goes out of scope. You can customize a class’s destructor by implementing the destroy
method.
Expanding on our Planet
example, if a Planet
needed to perform some cleanup, like releasing a unique ID, you’d add a destroy
method:
class ScannedPlanet {
diameter: i32;
// This ID needs specific cleanup logic
scan: i32;
// Destructors can also take `ref self:Self`
fn destroy[self: Self]() {
Core.Print("Make room in the telescope memory");
ExternalRegistry.release_scan(self.id);
}
}
The destroy
function has a standard definition, taking “ref self: Self”
as its implicit parameter (which can be converted to a value form “self:Self
”), and cannot have explicit parameters. This mechanism enhances practical safety and ensures cleanup actions can be performed automatically. Fields are destroyed in the reverse order they are created after the destructor returns. During interop, C++ has access to Carbon’s destructors, and Carbon has access to C++ destructors.
You might ask about virtual destructors. These are allowed, and required if you are trying to destroy a derived class through a pointer to its base class. It is erroneous behavior to destroy a derived class from a pointer to a base class that has a non-virtual destructor. With a virtual destructor, objects seen as abstract types also get cleaned up. If an abstract type needs a destructor, it should usually be virtual.
Conclusion
As we discussed in Part 1, classes are the primary mechanism for users to extend the Carbon type system. Part 2 talked about partial classes that make factory functions possible with abstract classes, and destructors that provide predictable ways to clean up your objects.
Check out these references for a deeper dive:
- Classes overview: https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/classes.md
- Inheritance proposal: https://github.com/carbon-language/carbon-lang/pull/777/files
- Destructors
- original proposal: Destructors #1154
- syntax proposal: Destructor syntax #5017 There are other aspects of Carbon’s class system still under active development. These include advanced features which will be explored in future discussions as their designs mature. Stay tuned for more!
Carbon out and about
- Podcast: Carbon and Modernizing C++ with Chandler Carruth
- Jon Ross-Perkins: Carbon Language: How We Compile (slides)
- Scott Hanselman demos Carbon/C++ interop running on a Commodore 64 (!)
Recent proposals and issues
Recently accepted proposals including:
-
#5689: Semantic Identity and Order-Dependent Resolution for Rewrite Constraints
-
#6008: Replace impl fn with override fn (new contributor!) Recently closed leads issues including:
-
#5930: How should modifiers for keyword operators be spelled?
-
#5884: Should type-checking inside a facet type constraint have access to rewrite constraints? If you want to keep up on Carbon’s proposals and issues, follow “Last Week In Carbon”. Check out the RSS feed!
Wrap-up
Don’t forget to subscribe! You can join announce@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 include more libraries, Wolff, Josh, and the Carbon team