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.
Work on the toolchain continues. We have made strides in interop! This example shows passing and returning integers to a C++ library.
// In `square.h`, a C++ header namespace MyNS { inline int square(int num) { return num * num; } } // In `square.carbon`, we can call into C++ import Cpp library "square.h"; import Core library "io"; fn Run() { Core.Print(Cpp.MyNS.square(6) + 6); }
Note that inline
is accepted by the Carbon compiler, but inlining itself is not yet supported. Try this example in the Compiler Explorer.
There has also been forward progress on generics, with a lot more implementation now in place. A key change is merging ("coalescing") equivalent instantiations of generics while lowering. This is Carbon's answer to automatically fixing C++ template bloat, as well as a key building block to have safety features participate in the type system. (See the PR here.)
Daily builds of the toolchain are available on our releases page, but for short experiments you can always use the Compiler Explorer which stays up-to-date.
Classes and instances in Carbon are designed to feel familiar to users of C++ (and, honestly, Java, C#, and Swift, too). Rather than explain too much in advance, let's present an example.
Note from Wolff: This example is about otters because it allows me to use the word "mustelid", which is an awesome word.
// It's good to define a package library "OtterBase"; // Define some fur statuses (enums are still TBD!) let WET: i32 = 0; let DRY: i32 = 1; // Define the class Mustelid; 'base' means it can be extended // Mustelids include weasels, otters, stoats, minks, and even badgers. base class Mustelid { // Fields, or member variables, are defined with 'var'. // These are public by default. var whisker_count: i32; // Using the 'protected' access modifier allows access by // members of this class and derived classes. // There's a default value here. protected var fur_status_: i32 = DRY; // A method that operates on an instance needs a "self". // Here self is a value, not a variable, so it cannot be changed // Again, this is public by default. fn GetFurStatus[self: Self]() -> FurStatus { // Accessing the field using dot notation and returning its value return self.fur_status_; } // In this case, we're using a pointer to self so we can change it. // As above, only derived classes can call this function. protected fn SetFurStatus[ref self: Self](status: i32) { self.fur_status_ = status; } }
This should all look familiar from other languages. As elsewhere in Carbon, values are not writable, so in methods that do mutations, self
must be a ref
(#5434). Class members can only be accessed using member-access syntax, which is unlike C++ where this->
is implicit when referencing a member.
Now, a Mustelid
instance.
fn Run() { // Can instantiate a base class. var maybe_a_badger: Mustelid = {.whisker_count = 50}; // Use a public method. Assert(maybe_a_badger.GetFurStatus() == DRY); // ❌ Fails! Can't use a protected method. maybe_a_badger.SetFurStatus(WET); // Oh, no, a terrible accident! A whisker is lost. maybe_a_badger.whisker_count -= 1; }
Mustelid
can be extended because it is a base
class. This allows us to create an Otter
class, and we will make Otter
abstract. (It has to be marked abstract because it contains an abstract method, but we could mark it abstract in any case.)
// Otters cannot be instantiated but can be extended. abstract class Otter { extend base: Mustelid; // An Otter-specific method. It cannot be overridden // because it is neither virtual nor abstract. fn Slide[self: Self]() { // TODO: Add sliding animations. Carbon.Print("An otter slides down a ramp."); } // Can be overridden in derived classes. virtual fn Play[ref self: Self]() { Carbon.Print("Otter at play!"); } // Abstract functions must be implemented in derived classes. abstract fn Swim[ref self: Self](); }
By default, functions are not virtual, so Slide()
cannot be overridden. However, Play
is allowed to be overridden and Swim
must be implemented in subclasses.
Carbon only supports single inheritance. This is to avoid the complexity and overhead that multiple inheritance brings, particularly since it is frequently discouraged in C++. (Sometimes, inheritance needs to be more complicated than single inheritance. Carbon has interfaces and mixins to help you with your complicated data structures, and we will cover them in future issues of Carbon Copy.)
There are lots of kinds of otters out there, but we'll differentiate only between river and sea otters for simplicity. This will be our final class structure:
class RiverOtter { extend base: Otter; // Implement the abstract Swim method for RiverOtter-specific behavior // 'override; is required, and makes it clear which methods are overrides override fn Swim[ref self: Self]() { // Swimming makes otters wet Carbon.Print("A river otter went swimming"); self.SetFurStatus(WET); } // Override Play override fn Play[ref self: Self]() { // Call the parent method via a proposed syntax // See following text. self.(Otter.Play)(); // Playing dries out river otters self.SetFurStatus(DRY); } // Add a RiverOtter-specific method fn CatchFish[ref self: Self]() { // Catching fish might involve getting wet, but Swim already does that. self.Swim(); Carbon.Print("A river otter caught a fish!") } }
Play
gets tricky, because we're trying to override it, but within that method call the base method without using virtual dispatch. This example uses a proposed syntax from this GitHub issue that would directly invoke a parent implementation.
Using our new class:
fn Run() { // As the implementation stands now, you must initialize all of the // base class members in this nested form. var riverOtter: RiverOtter = {.base = {.base = {.whisker_count = 30}}}; // Prints "Otter at play!" riverOtter.Play(); // Prints "1" (DRY) Core.Print(riverOtter.GetFurStatus()) // Grow another whisker. Base members can be accessed without indirection. riverOtter.whisker_count++; // Call a method // This will print "A river otter went swimming" and "A river otter caught a fish" riverOtter.CatchFish(); // Cast it back to an Otter let bob: Otter* = &riverOtter; // This will still call the overridden method and print out a message bob->CatchFish(); }
Let's take one more look at a subclass; this time a sea otter. For variety, let's define the methods outside the class definition.
class SeaOtter { extend base: Otter; // Sea otters have denser fur than other otters to keep warm in the ocean. // Members are public by default. var extra_fur_count: i32 = 10000; // Declarations to be implemented below. override fn SeaOtter.Swim[ref self: Self](); fn CreateSeaOtter() -> SeaOtter; } // This function is defined elsewhere. // Any kind of mustelid will do fn SwimInSaltWater(mustelid: Mustelid); // Implement the abstract Swim method for SeaOtter-specific behavior fn SeaOtter.Swim[ref self: Self]() { // This function does not modify its argument. SwimInSaltWater(self); SetFurStatus(WET); } // This factory returns new SeaOtters. // Note that there is a cast here, which converts the struct inside to the return type fn SeaOtter.Make() -> SeaOtter { return {.base = {.whisker_count = 100, .base = {.fur_status_ = WET}}}; }
And, try it out!
fn Run() { // Make a value expression instead of a variable let invincibleSeaOtter: SeaOtter = SeaOtter.Make(); // ❌ As sea otter is a value, it is invincible to whisker accidents invincibleSeaOtter.whiskerCount -= 1; var seaOtter: SeaOtter = SeaOtter.CreateSeaOtter(); seaOtter.Swim(); // Prints "1" (WET) Core.Print(seaOtter.GetFurStatus()); }
Note that this code will not compile with the toolchain as of publication. Along with calling base methods and ref
, discussed above, protected
is not yet available, nor are initial values for member variables.
As you may remember from Carbon Copy #5, fields of a struct are laid out in the order in which they are defined, so a Mustelid
's memory would be in the order:
whisker_count
fur_status_
For inheritance chains, each subsequent derived class appends on its members. In our example, only SeaOtter
s have another variable, extra_fur_count
, which would be appended onto the Mustelid
data section.
Instances of classes with virtual members have a hidden member called the vptr
, which points to its most-derived class's virtual function table (vtable)
. This should be a familiar design from other languages, where the virtual function table contains a list of function pointers for each of the virtual methods to handle dispatch. The table has the derived class's method pointers, unless the derived class does not override it, in which case it has the parent's.
Although most of these concepts concerning methods, members, and polymorphism are common among class systems, Carbon has taken a principled approach that reuses concepts from structs, variables, and values.
For example, Carbon classes have a focus on predictability and readability. Features that could potentially cause surprising behavior are made explicit in Carbon, such as requiring introducers for extendable classes and methods, and indicating where a function overrides or indicating whether self
is mutable. Likewise, each member and function must be explicitly set to something besides public
, which makes it easy to tell at a glance whether there are access modifiers, vs. having labels that may be far from their definition.
There is also a strong emphasis on simplicity. For example, multiple inheritance has always been a sharp sword, with descriptions of "the diamond of death" now almost 30 years old. In situations where you may need shared functionality across classes, Carbon provides other features like interfaces to separate interface from implementation (which we'll talk about soon!).
If you want to read the entire class design as approved now, it's here. This is an active area of toolchain implementation, so some of the features described here may change when they appear. (In fact, during the writing of this, the impl introducer became override!)
This is the first spotlight on classes. Next time, we'll delve deeper. If you have questions you'd like answered about classes, please write in and we can answer them!
Leads decisions:
impl fn
to override fn
?Merged proposals:
Other notes
We have ended the Carbon Weekly Meetup on Wednesdays (Pacific time). Thanks so much for joining us!
In its place, if you want frequent updates with lots of detail, we have a summary of activity in the Discussions tab under "Last Week In Carbon". There's even an RSS feed!
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, someday in Iowa,
Wolff, David, and the Carbon team