June 16, 2020·6 min read·engineeringgogenericsinterfaces

The Go generics draft replaces contracts with interface constraints

The June generics experiment removes contracts and adds a translation tool. Its purpose is to test whether the design solves concrete problems without committing Go to an implementation.

A second mechanism for expressing type requirements needs a reason to exist beside interfaces. The latest Go generics draft removes that duplication. Contracts disappear, and interface types constrain type parameters instead. The change follows feedback that the distinction between contracts and interfaces was confusing. It reduces the number of concepts a programmer must distinguish before understanding a generic function.

Today's generics update, by Ian Lance Taylor and Robert Griesemer, also releases a translation tool. It lets developers type-check and run programs using the draft design. This is an experiment with language behavior and usability. It is not an addition to the stable Go language, and the authors explicitly distinguish the translator from a possible compiler implementation.

I like Go's resistance to unnecessary private dialects. Generics should make repeated code easier to understand without making every library invent a separate vocabulary for constraints. Reusing interfaces gives this draft a more coherent starting point, while the experiment creates a way to judge that claim with actual programs.

Interfaces take over the constraint role

A type parameter needs a description of the operations that generic code may perform on it. The revised draft expresses that description through an interface constraint. The announcement allows type lists inside interfaces when those interfaces act as constraints. Earlier drafts placed those lists in contracts. More complicated cases use parameterized interface types.

That qualification about constraint use matters. The announcement does not make every interface value a container for a list of concrete types. It expands the role of interfaces in the proposed generic type system. Reading the change as an unconditional extension to ordinary interface values would blur the distinction the draft still needs.

The design question is whether the requirement on a type parameter is clear at the point where generic code uses it. If a generic operation requires an ordering operation, the constraint must express enough information to justify that operation. A method-only view of an existing interface does not by itself explain how the draft permits operators on a collection of numeric types.

Type lists address that part of the problem within the proposed constraint model. Their presence also means that familiar interface syntax now carries additional responsibilities in generic declarations. Removing contracts reduces conceptual duplication, but the experiment still needs to establish whether readers understand the resulting rules. Familiar terminology cannot substitute for useful type errors or a predictable explanation of permitted operations.

Running examples changes the kind of feedback

A written design lets reviewers reason about rules in isolation. A type checker lets them discover what those rules mean when a program is incomplete or incorrect. Error messages become part of the experiment because developers encounter the proposal through their mistakes as well as through successful examples.

The new tool translates generic source into ordinary Go. The resulting program can run through the existing implementation. That gives the community a way to try concrete use cases before the project commits to a language proposal. The announcement provides a special playground and a development branch for local experimentation.

The tool has limitations imposed by translation. The authors say a real implementation, if the design is accepted, will work differently. Consequently, a failure caused by the translator and a failure caused by the proposed type system are different observations. Useful feedback should identify which boundary it encountered instead of treating the prototype as a complete forecast of the final compiler.

Performance conclusions require the same distinction. A translation strategy selects particular representations and generated code. Those choices need not match the eventual compiler. The announced tool is suitable for asking whether a program expresses the intended abstraction and whether its errors are understandable. It does not establish the cost of a future implementation.

This separation keeps the experiment focused. Developers can report a missing operation or an awkward relationship between type parameters without first settling every question about code generation. Conversely, successful execution of a small example cannot establish that the proposed feature will be cheap in every larger program.

Demand needs an example attached

The 2019 developer survey provides context for the request. Among respondents who reported missing language capabilities as a blocker, 79% named generics. That result describes a subgroup, not the entire survey population. It establishes concentrated demand while leaving open which specific problems the language must solve.

The generics announcement asks that more precise question. If a programmer thinks generics would solve a problem, can this draft solve it usefully? That invites examples where the existing alternatives are inadequate. A useful example identifies the repetition, lost type information, or awkward API that motivates the abstraction. It then shows whether the proposed constraints state the requirement clearly.

For example, a reusable container needs to preserve the relationship between the values it accepts and those it returns. A generic algorithm may instead need a specific operation on its inputs. These are related motivations but they place different demands on the constraint design. An experiment that handles one cannot automatically stand for every use case described as needing generics.

The shape of the consuming code matters as well. Eliminating duplicate implementation code helps little if callers must understand a complicated parameter relationship to perform a simple operation. A complete example therefore includes both the generic declaration and its ordinary call sites. That is where reviewers can see whether the abstraction preserves the directness expected of Go APIs.

I regard those examples as more informative than a general vote for or against generics. The language needs an account of which repeated structures justify abstraction. A runnable prototype makes it possible to compare that account with the code a caller actually writes.

Formal work addresses another part of the design

The announcement acknowledges Philip Wadler and collaborators for formal work on generics. The first version of Featherweight Go, submitted in May, studies generics in a restricted language. Its abstract describes structural subtyping and formalizes a monomorphization approach. The work addresses theoretical questions that small playground examples cannot resolve by themselves.

A restricted calculus and an experimental translator answer different questions. The formal model can make assumptions and relationships precise. The translator exposes usability problems in programs closer to what developers want to write. Neither eliminates the need for the other, and neither by itself constitutes an accepted change to the production language.

The implementation distinction remains especially relevant here. A formal treatment of one code-generation approach does not establish that the announced translation tool is the final implementation strategy. The blog explicitly says that direct compiler work has only begun to be sketched. Readers should keep the theoretical model, experiment, and possible production implementation separate when assessing claims about cost.

The announcement asks whether generic code still feels like Go and which surprises programmers encounter. These questions can be made concrete through review. A reader can try to explain why a rejected operation fails using only the declared constraint. If that explanation requires knowledge of the translation output, the abstraction has not supplied a sufficient account of its own rules. The same review can examine an accepted program whose meaning surprised its author. Successful type checking establishes agreement with the draft rules, but the experiment also asks whether those rules match the expectations encouraged by existing Go code. That is a distinct source of feedback from reporting a compiler crash.

The process still has an acceptance decision

The next proposed step is conditional. If the revised draft is well received and does not require substantial changes, the team would proceed to a formal language-change proposal. The announcement makes no definite prediction. Experimentation supplies evidence for that decision, including evidence that the design needs further revision.

The authors also separate design discussion from tool defects. Language feedback belongs on the mailing list, with a generics subject marker and separate threads for distinct topics. Bugs in the checker or translator belong in the issue tracker under the cmd/go2go prefix. That division recognizes that a lengthy argument about semantics differs from a reproducible failure in an implementation.

The valuable next result is a body of examples that the draft handles clearly, together with specific cases it handles badly. Those examples need to include diagnostics and ordinary use, not only compact demonstrations of successful generic declarations. Removing contracts makes the vocabulary smaller. The checker and translator now let developers examine whether that smaller vocabulary expresses the relationships their libraries actually require.