Carbonated: Why Google’s Enthusiasm for Go Fizzled as a C++ Successor
“Within C++, there is a much smaller and cleaner language struggling to get out”
– Bjarne Stroustrup, creator of C++ in The Design and Evolution of C++
Developers are talking about Google’s latest creation: Carbon, a supposed wunderkind programming language that will save the technoverse from C++ and serve as its successor or replacement. Just like a rehashed Hollywood blockbuster about a supposed messiah, we’ve heard this story before. The 2000s saw more than one language try to fix C++’s minuses. The two big ones were Rust (backed by Mozilla) and Go (a.k.a. Golang, initiated by Google).
We should note that Google had been working on the Carbon programming language for at least two years before announcing it. The oldest pull request in the Carbon git is from April 30, 2020. Google’s team has embedded a lot of work into the repository already, including detailed outlines of the philosophy and goals for portions of and the entirety of the project.
History Threepeating Itself
C++’s creators had their own goals: to build in higher layers of abstraction into C. They took on immense amounts of “technical debt” from C, ensuring C++ could be backward-compatible to compile C code. But over time, accommodating that with feature after feature made C++ overly complicated. Now, building on decades of experimentation and backward compatibility, C++ just literally might be the most convoluted language out there.
That got a lot of developers motivated in the 2000s to fix the new problem.
Google conceived of Go as a replacement for C++, the same motivations behind another major language: Rust. Go’s initial approach was celebrated: something straightforward, easy to learn, and simple to understand. When Go and Rust were both production-ready, Go could tout that simplicity against Rust’s extreme learning curve. Additionally, the performance advantage Rust could claim over Go wasn’t worth the tradeoff to many developers. Rust sacrificed compile time for runtime, for instance.
So are we about to go reiterate this process (tri-iterate? “three-iterate”?)? 1) C → C++, 2) C++ → Go, 3) Go → Carbon?
Well, yes.
Go has failed to replace C++. But WHY? Why did it fail? And why did Google feel that the best way forward was to create another new language to get around it? Here are the major reasons:
1. Go-C++ Interoperability
Anything taking over for C++ has to at least work with C++. While the cgo API is definitely there, using the two together is not that much fun.
For most practical use cases, you will find yourself wrapping your C++ code with a C API (it is called CGO after all!) which can amount to a fair amount of boilerplate code.
Along the way, you are going to lose some type safety, proper destructor support, and have to work around memory management. Add to that the fact you will likely find yourself writing an abstraction layer on the Go side. Before you send it through, you are quite likely to end up with a Frankenstein project, just like this team.
2. Interoperability Paradigm Shifts
Go has a peculiar, but highly efficient, concurrency model focused on goroutines. Using user mode scheduling along with miniature, dynamically growing stacks will get you a level of parallelism you can only dream about in other languages.
Unfortunately, this magic is utterly unavailable for your C++ code, meaning every cgo invocation goes through a move from a mini-stack to a full-blown stack and back again. As you might guess, this is quite heavy on performance, making every invocation a significant burden.
Working around this limitation to keep your interoperability API efficient will likely make it even more complex and cumbersome to use, while winning you some additional boilerplate code to write.
4. Go’s Runtime
Go has a fairly large and complex runtime, which makes it less desirable than C++ in many use cases. While the runtime comes with a lot of magic tricks such as best-in-class garbage collection, it’s not without its baggage. While you can hardly compare it to the Java Virtual Machine languages or .NET runtime, it’s not something you can easily fit into embedded devices, operating system kernel mode, or other similar environments.
5. Pointers are Important
When writing system-level software, raw pointers are a key element in getting the best performance from your hardware and operating system. Whether you are offloading encryption to the CPU, checksum calculations to the network adapter, or memory operations to the DMA controller, you need easy access to the underlying memory.
Go’s approach for pointers and direct memory access is far from its strong suit, which can make writing encryption, networking, or storage software a headache.
What Carbon Will Try to Do
What does Carbon want to do differently to ensure it succeeds as an adequate C++ successor where Go failed? They’re going to tie the two languages together as tightly as possible with as little bridge code as possible as well. And as a caveat, Go did succeed in some things, such as being easy to learn. Carbon’s builders plan to emulate that.
Related goals include minimal bridge code between the two languages, support for basic C interoperability, support for advanced C++ features, and mixing the two languages’ toolchains.
- Quick adoption: It’s supposed to be a piece of cake for C++ developers and easy to work with immediately for C++ applications. Replacement for C++ isn’t a snap process, but it can be made relatively quicker.
- Bidirectional interoperability with C++: Packages and libraries made for C++ will work for Carbon, and libraries or packages made for Carbon will work for C++. A formal Carbon-C++interoperability layer will access certain C++ APIs from Carbon and vice versa.
- Memory Safety: You have more control over memory safety so you don’t have to compromise on things like compilation time like you have to by default in Rust. You can run in three modes:
debug
,hardened
, andperformance
. C++ enables uninitialized variables which can later cause memory access bugs. - Generics: C++ relies on templates to support parameterized types, but they can be complex. C++ templates also bog down compile time. Generics, in C# for instance, work in runtime. The idea behind Carbon generics is to check function calls independently, send earlier error message when necessary, and speed up builds.
- Memory access: Carbon kept the basic concept of pointers’ value while giving up on most of their complexity. Carbon gives you the two most basic operations of
derefence
andaddress-of
, to make your life easier.
Wait and C
Google first announced Go in 2009, but the language didn’t hit full production readiness until 2012 (at Rookout, we added production grade support in 2021. Thanks for waiting!). Expect a similar amount of time between now and Carbon’s production readiness. And yet, being backed by Google will likely make Carbon a contender in the A-league of software languages, not to be known merely as C++’s substitute. In its early days, developers should still expect to write Carbon code in a text editor and do things the hard way, but if Carbon grows as planned, that might not be necessary for near-to-mid-future classes of devs. But then again, it might end up like D.