Go 2 Generics: some features of contracts that I like
A major part of the Go 2 generics draft design is its use of contracts to specify constraints on what types are accepted by generic functions, which are essentially formalized function bodies. On the one hand I think that contracts today are too clever, but on the other hand I think that interfaces are not the right model for type constraints. Although the existing first Go 2 generics draft is probably dead at this point, since it's clear that the community is split, I still feel like writing down some features of contracts that I like (partly because I'd like to see them preserved in any future proposal).
The first thing is that contracts are effectively an API and are explicitly decoupled from the implementation of generic functions. This is good for all of the reasons that an API is good; it both constrains and frees the implementer, and lowers the chances that either users or implementers (or both) are quietly relying on accidental aspects of the implementation. As an API, in the best case contracts can provide straightforward documentation that is independent from a potentially complex implementation.
(I think it would be hard to provide this with the current version of contracts, due to them being too clever, but that's a separate thing.)
Because contracts have an independent existence, multiple things can all use the same contract. Because they're all using the same contract, it's guaranteed that they all accept the same types and that a type that can be used with one can be used with all. This is directly handy for methods of generic types (which might be a bit hard otherwise), but I think it's a good thing in general; it makes it both clear and easy to create a related group of generic functions that all operate on the same things, for example.
(In theory you can do this even if each implementation has a separate expression of the type constraints; you just make all of the separate type constraints be the same. But in practice this is prone to various issues and mistakes. A single contract both makes it immediately clear that everything accepts the same type and enforces that they do.)
The final thing I like about contracts is that they can explicitly
use (and thus require)
struct fields. This is a somewhat contentious
issue, but I don't like getters and setters
and I think that allowing for direct field access is more in the
spirit of Go's straightforward efficiency. Perhaps a sufficiently
clever compiler could inline those little getter and setter functions,
but with direct struct fields you don't have to count on that.
(I also feel that direct access to struct fields is in keeping with
direct access to type values, which very much should be part of any
generics implementation. If you cannot write
generic functions that are as efficient as the non-generic versions,
the generics approach is wrong.)