Everyone is excited that after a decade or so of devs asking for generics, the Go programming language is getting generic types and functions in Go 1.18 in Q1 2022. Generics are no doubt going to lead to a lot of experiments, some good, some bad, some just weird. Go 1.18 is also poised to lead to an increase in software reliability by including fuzzing as part of the standard testing package. But today, I want to look at some minor changes in Go 1.18 that might otherwise get lost in all the excitement around the marquee features.
I want to poke around the Go compiler and understand some internals for my next RC project. I want to add a new feature that does something new to the compiler. I found this fantastic article Go compiler internals: adding a new statement to Go by Eli Bendersky, precisely what I want!
Before doing anything significant, I wanted to make a tiny change to get familiar with the toolchain. First, I decided to add an alias
for. However, the compiler codebase already uses keyword loop as a label at many places, so I changed my alias. Also, instead of
for, I decided to add an alias for
var. The rest of this post summarises how I accomplished this and my learnings along the way.
Any form of generics needs some way to constrain what types can be used with your generic functions (or generic types with methods), so that you can do useful things with them. The Go team’s initial version of their generics proposal famously had a complicated method for this called “contracts”, which looked like function bodies with some expressions in them. I (and other people) thought that this was rather too clever. After a lot of feedback, the Go team’s revised second and third proposal took a more boring approach; the final design that was proposed and accepted used a version of Go interfaces for this purpose.
Go is a programming language which passes by value, which effectively means that if you give a value as a parameter to a function, the received value within the function is actually a copy of the original. You can modify it however you wish and your changes will not affect the original value or escape the function scope. This is in contrast to some languages which pass values by reference instead of copying them.
Newcomers to Go, however, will quickly discover that it doesn’t feel as though this is what is really happening in practice. You pass a map into a function only to find that if that function modifies the map, it gets modified everywhere. Worse, the program might just break in mysterious ways or panic altogether! What gives?
When I wrote about why it matters that map values are unaddressable in Go, there were a set of Twitter replies from Sean Barrett:
Knowing none of the details & not being a go programmer, I would have guessed that map values aren’t addressable because they’re in a dynamically-sized hash table so they need to get relocated behind the user’s back; getting the address of a value slot would break that.
But I’d also have assumed Go has dynamically-extensible arrays, and the same argument would apply in that case, so maybe not?
This sparked an article about how Go maps store their values and keys, so today I’m writing about the second part of Barrett’s reply, about “dynamically-extensible arrays”, because the situation here in Go is peculiar (especially from the perspective of a C or C++ programmer trying to extend their intuitions to Go). Put simply, Go has pointers and it has something like dynamically extensible arrays, but in practice you can’t use pointers to slices or slice elements. Trying to combine the two is a recipe for pain, confusion, and weird problems.
Go’s last standing major weakness is error handling. A few years ago the list was much longer, with the language missing an adequate package manager, system for pulling static assets into a binary, and generics. But now, the first two have already been addressed been address with Go Modules in 1.11 and
go:embedin 1.16, and generics are expected to be in beta form by Go 1.18’s release in December. Errors are the last major omission.
The current Go language memory model was written in 2009, with minor updates since. It is clear that there are at least a few details that we should add to the current memory model, among them an explicit endorsement of race detectors and a clear statement of how the APIs in
This post restates Go’s overall philosophy and the current memory model and then outlines the relatively small adjustments I believe that we should make to the Go memory model. It assumes the background presented in the earlier posts “Hardware Memory Models” and “Programming Language Memory Models.”
I have opened a GitHub discussion to collect feedback on the ideas presented here. Based on that feedback, I intend to prepare a formal Go proposal later this month. The use of GitHub discussions is itself a bit of an experiment, continuing to try to find a reasonable way to scale discussions of important changes.
To kick things off, why are constants good? Three things spring to mind:
Immutability. Constants are one of the few ways we have in Go to express immutability to the compiler.
Clarity. Constants give us a way to extract magic numbers from our code, giving them names and semantic meaning.
Performance. The ability to express to the compiler that something will not change is key as it unlocks optimisations such as constant folding, constant propagation, branch and dead code elimination.
But these are generic use cases for constants, they apply to any language. Let’s talk about some of the properties of Go’s constants.
Go’s place between C and Python in terms of abstraction and garbage collection memory management model has made it attractive to programmers looking for a fast but reasonably high level language. However, there is no free lunch. Go’s abstractions, especially with regards to allocation, come with a cost. This article will show ways to measure and reduce this cost.
A change recently landed in the development version of Go, and will be in Go 1.17, with the title of doc/go1.17: note deprecation of ‘go get’ for installing commands. The actual updated documentation (from the current draft release notes) says:
go getprints a deprecation warning when installing commands outside the main module (without the
go install cmd@versionshould be used instead to install a command at a specific version, using a suffix like
@v1.2.3. In Go 1.18, the
-dflag will always be enabled, and
go getwill only be used to change dependencies in
I have some views on this. The first one is that this is a pretty abrupt deprecation schedule. Go will be printing a warning for only one release, which means six months or so. People who don’t update to every Go version (or their operating system’s packaged version doesn’t) will go from ‘
go get’ working normally to install programs to having it fail completely.