Closing Words: Where Is Go Headed?
Having read this far, the reader has walked with the book through Go’s five major parts: its panorama and history, its language features, concurrency, memory, and the compiler and toolchain. As a conclusion, let us step back from the specific implementation details and talk about the legacy Go leaves behind, the challenges it currently faces, and where it might be headed.
What Go Leaves Behind
Looking back over the whole book, Go’s deepest legacy is perhaps not any single concrete technique, but a design stance it demonstrates again and again: place complexity where it disturbs the user least, and let simplicity be the result rather than the starting point. This thread shows itself everywhere in the book. The scheduler (9) hides the complexity of thread management inside the runtime and gives the user only a single go keyword; garbage collection (13) keeps all the subtlety of sub-millisecond pauses internal, so the user needs almost no tuning; generics (8) waited thirteen years just to find an implementation that sacrifices neither compilation speed nor simplicity; the module system (17) replaced the industry’s usual constraint solver with minimal version selection, the simplest possible algorithm. Each of these answers the same question: by what right does a given piece of complexity deserve to be introduced?
It is precisely this restraint that lets Go, more than a decade after its birth, still allow users to write backward-compatible programs, and still stand in industry on the ground of being “simple and engineering-friendly.” It proves one thing: a successful language need not rely on the strongest expressive power, but may instead rely on the most clear-headed trade-offs.
The Challenges It Faces
Go is not without its costs. The book also points out, again and again, the other side of its trade-offs: the memory fragmentation brought by a non-moving GC has to be absorbed by size classes (12.1); the screenfuls of if err != nil (7) are the inevitable bill of “errors are values,” and the various syntactic attempts to write it shorter have so far not been accepted (7.5); the dictionary indirection of generics is sometimes slower on the hot path instead of faster (8.4); the cross-boundary overhead of cgo makes interoperation between Go and the C world far from cheap (15.6). None of these is a bug; they are the boundaries carved by design trade-offs. Only by understanding them does one truly understand Go.
Where It Might Be Headed
Go’s evolution is still going on, and most of it follows the same rhythm: first ship the smallest usable version, observe the needs in real use, then expand cautiously. The book already shows extensions of this line: garbage collection is evolving toward better cache locality (Green Tea GC, 13.11); the compiler is pushing optimization from static guessing toward being data-driven (PGO, 15.3); the runtime overhead of generics is being continually polished; observability (16) and structured diagnostics are still being filled in. What can be foreseen is that, whatever is added, Go in all likelihood will not trade away these few bottom lines: compilation speed, backward compatibility, and “you can understand it by reading a single line of code.” Its future is most likely not some version with an explosion of features, but a continuation of this slow walk in small steps.
The Author’s Closing Remarks
Writing this book was extremely difficult. Part of that difficulty came from the complexity brought by the source code’s own development process: during the writing of this book, the code itself kept evolving. The book was first written for Go 1.10, and to ensure that the content could both keep pace with the evolution of the code and maintain the quality of the writing, one had to stay attentive throughout to those designs that are the most fundamental, the most general, and the most classic and enduring.
The study of Go’s source code is not confined to the runtime code directly related to user-space code; that is only a small part of Go’s source. Fully understanding Go’s source requires not only a systematic grasp of theory across many areas, but also a command of the coordination between Go’s own compiler and runtime. This basically determines that this book has a high starting point, both for writing and for reading. The complexity also comes from the mutual coupling between the various standard libraries and runtime modules. Because Go builds a layer of runtime mechanism on top of the traditional program flow, many standard library designs that are traditional in the conventional sense become very intricate. Unless some standard library or some component is purely logic code that does not depend on the underlying implementation, very little content can stand as a self-contained chapter without forming connections to other chapters, which raised the difficulty of a writing process that presents knowledge linearly. Besides the runtime and the compiler, the standard libraries discussed in the book still required the author to be quite familiar with the coupling relationships among the various modules before writing, and they likewise require the reader to keep sketching these coupling relationships in mind during reading, which further raised the threshold for reading this book.
The book was written over roughly two-plus years, from the end of 2018 until now, growing from an initial source-code analysis of the runtime scheduler, into an analysis of the runtime code, and finally into the present analysis of the entire Go source code that even includes the compiler, the linker, and the toolchain. In short, a great many changes were made to the book’s organizational structure during the writing process; readers who are interested may also browse the book’s commit history.
Fortunately, this difficulty has now been overcome. Looking back on this process, it was accompanied by many thoughts of giving up. One of the main reasons was that the content was overly complex and the return on investment from finishing the whole book was extremely low. Yet in the end I persevered, which owed no small amount to the support of the enthusiastic readers who followed the writing of this book.
Finally, the author hopes that the reader has already gained the knowledge they were hoping for from this book, and has deepened their own understanding of the Go language, coming to understand that writing a successful programming language requires not only systematic and complex engineering and optimization knowledge, but, even more, the engineering loop of continual thinking, discussion, practice, and redesign of the problem. Although the study of the source code has now ended, applying the knowledge gained in that study to production practice to build more value will be a brand-new starting point of its own.