I have been working with Go language intensively in the past year. I think it’s time for me to write something about my thoughts about Go. I was mostly working with C++ before I started my project with Go. Thus I want to focus on comparing the two programming languages: what I don’t like about C++ and how Go solved those problems. And finally, what I don’t like about Go and why Go won’t replace C++.
This is not a tutorial to teach C++ programmers to learn Go. I will not spend any time on those useful but boring stuffs like the syntax differences between C++ and Go. There are other people who are nice enough to write this kind of tutorials. Google them yourself if you need to.
The Problems with C++
I think the most important characteristic of C++ is that it is backward compatible with the good old C language. Most valid C programs are also valid C++ programs. (Stop bitching about those petty exceptions.) It is both a blessing and a curse which makes C++ what it is today.
The upside is obvious. C++ excels everywhere C excels, and C++ excels C in almost every aspect. Almost all large and long living C projects I know were converted to C++ today. C is the programming language of Windows, MacOS, and Linux. Thus C++ programs preserve the “native” feeling of C programs in all 3 mainstream operating systems. Also, C is one of the most low-level high-level programming languages. C++, which does not have garbage collection or JVM (or equivalents), runs almost as fast as C. Thus almost all performance critical codes, like operating systems or graphic codes, are written in C++ nowadays.
However, almost all the downsides of C++ are caused by the fact that C++ is backward compatible with C. Here are three things I found most unbearable while working on projects in C++.
Unnatural Modern Features
The C++ standard library, and all the new features introduced of C++11 are all wonderful for C programmers. However, to avoid naming conflicts, all these essential 21th century features have ridiculous long names.
For example, to use the sane string type instead of the totally fucked up C-style string, you have to write
If this the ugliness of the code above is still bearable, now consider writing a 21th century C++ program where you use only smart pointers and std::vector’s. Now you have to write something like the following code everywhere in your program
std::shared_ptr<std::vector<std::shared_ptr > > do_something(
std::shared_ptr<std::vector<std::shared_ptr > > array1,
std::shared_ptr<std::vector<std::shared_ptr > > array2);
I would be very impressed if you could tell the code above is a function declaration in 3 seconds.
No, you can’t use “using namespace” or define macros to create your own C++ dialect. By doing so, especially in header files, you would create more headaches than the problems it solves.
Yes, modern IDEs with auto-completion would help you write these kind of code, but the resulting code would still be extremely ugly for your coworkers to understand.
Lack of Native Build System
The C was designed in the dark age when every programmer needs to know what a linker is. Unfortunately C++ keeps all the terrible things C has in terms of building process. To make sure your C/C++ programs work, you need to spend substantial amount of work to handle lots of unnecessary and counterproductive things. You need to take care of forward declarations. You need to handle circular dependencies. You need to write exactly the same function declaration twice, one in the header file, and one in the c file. You need to open two files and do the same thing twice if you want to make a little change to your function declaration. You need to write an include guard in every header file and ask yourself why your are doing this kind of robotic work like a Rust Belt worker (before they got laid off and voted for Trump, of course). And there is the worst nightmare, when you found a bug introduced somewhere in the including chain of header files, like someone put a “using namespace xxx;” in a header file and your identifier was resolved to a different variable, you had to reserve a workday (at least) to track down the cause.
None of other living programming languages has this kind of problem. The build system (or interpreter) that comes with it knows exactly what function or class declaration you are referring to without writing any header files.
The big selling point of C++ initially was its support to object-oriented programming (OOP). However, the problem with the header files become exponentially worse with OOP.
While writing a component of a C program, you may simply put all external functions in a single header file. If you write a object-oriented C++ program, there is no difference between external functions and internal functions. Your class needs to be used externally. Therefore the class definition needs to be exposed in the header file, and thus all those private member variables, and those private help methods with little interest to outsiders, all have to be written inside the header file.
What’s worse, the cpp file is only allowed to contain the definition of the body of the methods. Now if you want to understand a class definition, you need to open two files simultaneously. You need to go to the header file to find the method you are interested in, then search in the cpp file to find the actual definition of the method, and then go back to the header file if you want to know about the definition of some private member variables.
In Java or C#, all you need to do is using an editor with code folding. And you don’t need to write every method definition twice. I learned OOP with C++. After I picked up Java and C#, going back writing object-oriented code in C++ is like in hell. In the end, you have to admit that as std::vector and std::shared_ptr, OOP is simply a flavor, not a native feature of C++. C++ was not designed as an object-oriented language.
The Making of Go
What I Love About Go
Less than one week after I started working my Go projects, I fell in love with the language. It feels like C++. Its syntax is C-like. It is compiled. And most importantly, it has pointers. At the same time, it address all the pain points of C++. It has native vectors (or ArrayLists in Java), strings, maps (Dictionaries), and multithreading out of the box. It eliminates header files. It even goes so radical that it eliminates semicolons and parentheses.
Another interesting feature of Go is the elimination of classes. There is only interfaces and struct with methods. In the first few weeks, I didn’t spend any time thinking about this design. But when I started to think about it, I got my aha moment. Suddenly all the bad experiences with OOP came up. Don’t get me wrong. I love OOP. It’s a great tool. But problems arise when people start to abuse OOP. The problems get worse when OOP abusing become a cult. The problems get to its worst point when your director is an OOP cultist. I will never forget the moment when I was asked to rewrite my elegant functional programming approach taking an enum as input to an astonishing 7 file jungle with multilevel inheritance.
Go still has all the good things of OOP. You can still write dog.bark() instead of animalBark(dog). You can still have abstracted interfaces and separate interfaces and implementations. Class composition is still allowed. But all those cult favorites and problem causing features, like setters and getters, protection, and inheritance, are all banned.
It’s possible to write code without any inheritance or protection in Java, C#, and C++. What’s big about the design is that Go is the first cool language to ban these features. It is announcing that OOP is not sexy any more. And next time I see an OOP cultist tried to mess up with my code, I can stand up, pound the desk, and ask them to shut the fuck up.
Problems with Go
Despite my love towards Go, there is a single problem haunting me day in and day out: the pointers.
My mother tongue is Pascal. I learned it when I was 12. I learn C/C++ when I was 15. By the time I started learning Java in college, I was already an expert Pascal and C/C++ programmer and knew pointers inside out. Initially I feel a little bit confused about the Java variables because some are actual variables but some are pointers. But later everything become clear: only numbers, characters, and booleans (, or primitive types as called in Java) are variables, everything else is pointer.
Unlike Ruby on Rails, where the magic helps you build a Facebook clone with a single command, the Go pointer magic only helps you type one less character but totally messes up your brain. I don’t know if this feature was designed to woo those mediocre Java programmers to use Go instead. But it definitely turns rockstar programmers (like me) down.
Another problem of Go is that Go doesn’t excel where C excels. Go cannot use POSIX or WinAPI directly. Go has garbage collection which slows everything down.
As a result, Go has zero chance to challenge C or C++. The statistics agree with my reasoning. C and C++ remain unchallenged but Go is eating up the market share of Java in light speed, especially as a web server side language. Actually I couldn’t see any reason not to use Go instead of Java or C# for server side development.
How Go Can Replace C++
If Go really wants to challenge C++, there are 3 things they must do: allow real pointers and eliminate the magic, turn off garbage collection, and support native calls to POSIX, WinAPI, DirectX, etc. They can create a dialect of Go with these changes with little effort.
Unfortunately I don’t think these changes would ever happen. Apple has no problem making a dropdown to turn smart pointers (ARC) on and off so people can easily switch between two Objective-C dialects. But those nerds in Google would consider this blasphemy because it damages the purity of the superior language they designed. Also it would be impossible to convince them how important it is to give native support to WinAPI and DirectX, because for them, Windows programming is the least cool thing to do.