Go 2 Generics: contracts are now boring (and that's good)
At the end of July, the Go team put out a revised Go 2 generics design which completely redid contracts, which specify constraints on what types generic functions and so on accept (this revised design is not yet linked on the wiki feedback page, to my surprise, but as far as I know it's fully official). I've been considering my views on the revised design since it came out, and the short summary is that I don't have much to say because, to put it one way, the new design for contracts is boring.
You can say a lot of things about contracts in the original design, but they certainly weren't boring. Although contracts reused existing Go syntax (for function bodies), they were a significant departure from anything else in Go, a departure that I felt was too clever. In an apparent attempt to minimize new syntax, the original contracts design often said things indirectly, leading to issues like there being many ways to express the same constraints (although maybe Go could have required minimal contracts).
The new design for contracts does away with all of that. Contracts
use a new syntax, although it reuses many of the syntax elements
of normal Go, and the syntax is basically minimal and directly says
what constraints it means. Tricky things that were previously
expressed only by implication are now said literally, for example
to constrain a type to something based on integers. In the one case
so far where a type constraint would still would be hard to express,
the Go team introduced a predeclared contract called
rather than try to be clever. As the proposal itself says:
There will always be a limited number of predeclared types, and a limited number of operators that those types support. Future language changes will not fundamentally change those facts, so this approach will continue to be useful.
This strikes me as a very Go approach to the problem. It's not clever and exciting, but it works (and it's clear and explicit).
Another decision that is not elegant but that solves a real potential confusion is that all method calls in a generic function body will be pointer method calls. The rules on addressable values and pointer versus value methods are somewhat confusing and obscure, so the Go team has decided to make them moot rather than to try to be clever. However inelegant this feels, it will probably make my life easier if or when I need to deal with generics.
(The design also permits you to specify that certain methods must be pointer methods, although what types satisfy the resulting contract is perhaps a bit too clever and obscure.)
The revised contracts design preserves almost everything that I
liked about contracts in the first design. The
one thing it dropped that I wish otherwise about is that you can
no longer require that types have certain fields (which implicitly
required them to be
structs). In practice inlined getter and
setter methods can be just as efficient, even if I don't like
them, and it always was something where
the usage cases were unclear.
I don't know if the current Go 2 generics and contracts design is the absolutely right one or not, but I do now feel that it's not a mistake in the way that the first version felt like. I do hope that people try to write generics code using this design if and when it lands in some experimental form in some version of Go or is otherwise made available, because I suspect that that's the major way we're going to find any remaining rough edges and pain points.