Go proposes type parameters with explicit constraints
The formal generics proposal brings reusable algorithms into Go while keeping their type requirements visible. Its history explains why interfaces replaced a separate contracts mechanism.
Go has reached a formal proposal for generics after a design process that repeatedly changed how programmers would express the requirements of generic code. The interesting part is the attempt to make those requirements understandable at a function declaration. A caller needs to know which types an algorithm accepts. The author of the algorithm needs a definite account of which operations its implementation may use. A reusable function becomes difficult to maintain when those two accounts can drift apart.
The January 12 announcement says the team now considers the design ready for the language change process. It proposes type parameters for functions and types. Acceptance remains ahead of it, and the implementation target is conditional. The proposal deserves attention now because it supplies a concrete answer to a long-running question about Go's abstractions.
I value Go's resistance to private dialects inside a codebase. A useful addition has to reduce repetition without making a reader reconstruct an author's abstraction system. Explicit constraints are the part of this proposal that makes that balance plausible.
Why existing interfaces leave a gap
Ian Lance Taylor's 2019 explanation of the case for generics starts with ordinary algorithms and data structures. Reversing a slice is a useful example. The assignments that exchange elements do not depend on whether the element is an integer or a string. Writing separate versions repeats the algorithm because the element types differ, even though the operations that matter remain the same.
An interface can describe behavior shared by different concrete types. It does not automatically make a slice of concrete values interchangeable with a slice of interface values. Taylor's discussion of converting values to strings calls out this distinction. A slice whose element type implements a String method still has its own representation. The existence of an appropriate interface does not turn that slice into a slice of that interface.
This matters when the relationship between inputs and outputs carries useful information. An algorithm that receives a slice of a particular element type may need to return values of that same type. A generic declaration can express the relationship once, using a parameter that stands for the type. The caller supplies the concrete type while retaining the relationship in the resulting function.
The benefit is especially clear for containers. A data structure that stores one chosen element type should be able to expose that choice in its operations. The 2019 article uses a binary tree with a comparison function. The tree's implementation handles its links and elements, while the supplied comparison defines ordering. Those are separate responsibilities, and neither requires the tree author to know every possible element type in advance.
That example also shows why generics should not be judged by counting duplicated lines alone. A common declaration can preserve a type relationship that would otherwise be repeated or checked through a less specific interface. The justification is strongest where the reusable operation has a stable meaning across its supported types.
What the January proposal says
The opening description of proposal 43651 gives the proposed function form as func F[T any](p T). Square brackets contain the type parameter list. Ordinary parameters follow in their usual parentheses. The identifier T can appear in the types of ordinary parameters and inside the function body.
The same description permits parameterized type declarations. It uses type MySlice[T any] []T as its short example. A type parameter has a constraint, and the constraint is an interface type. The proposed predeclared name any permits every type as an argument. It does not grant every possible operation on values of that type.
A small declaration can therefore preserve an element type without requiring any particular methods. The following is an illustration of the proposal's syntax, not code supported by a released Go compiler today.
// First returns the first element and whether the slice is nonempty.
func First[T any](values []T) (T, bool) {
if len(values) == 0 {
var zero T
return zero, false
}
return values[0], true
}
The empty case needs a value of T, so it declares a variable of that type and returns its zero value. The boolean distinguishes an empty input from a first element whose value happens to be zero. Nothing in this function requires arithmetic or a method on an element. Its constraint can remain general because its operations do not depend on those capabilities.
The declaration expresses more than the fact that the function accepts a slice. The returned value has the slice's element type. A caller does not need a separate declaration of the same algorithm for each element type. The proposal's overview also allows type inference to omit explicit type arguments in common function calls, reducing the amount of type syntax at use sites.
The important restriction is that a generic body may use only operations its constraint permits. An unconstrained element cannot acquire an arbitrary method merely because one caller happens to pass a type with that method. The declaration must state the requirement. This gives the reader a place to inspect the algorithm's assumptions before reading its implementation.
Why contracts disappeared
The June 2020 design update explains a substantial simplification. An earlier design used contracts to describe generic requirements. Taylor and Robert Griesemer reported that the difference between contracts and interface types confused readers. The revised draft removed that distinction and used interfaces to constrain type parameters.
The 2019 article makes the history concrete. Its examples include a Stringer contract requiring a String method and an Ordered contract listing types that support ordering. Those examples use syntax from that earlier draft. They are useful evidence of the design problem, but they should not be copied as the current proposal's notation.
By June, the designers had moved type lists into interfaces used as constraints. The January proposal retains the idea that an interface constraint can list predeclared types. This is how the design extends beyond constraints described solely by methods. An algorithm that uses an operator needs a way to restrict its type arguments to types that support that operation.
Reusing interfaces reduces the number of separate concepts a Go programmer must learn, although it also asks interfaces to express more. The distinction between an interface used as a value type and an interface used as a constraint still needs explanation. Eliminating contracts does not eliminate the underlying problem of specifying permitted operations. It changes where that specification lives.
I prefer that direction because a constraint belongs with the requirement it describes. Go programmers already read interfaces to understand required behavior. Extending that vocabulary seems more consistent with the language than introducing a parallel system for a closely related job.
Experiments before a language commitment
The June update also describes a type checker and a translation tool for trying the draft. This tool checks generic code and translates it into ordinary Go. Its authors explicitly distinguish that translation from the implementation a future accepted language feature would use. Its purpose is to let people investigate what generic programs actually look like.
That distinction changes what evidence an experiment can provide. A successful example can show that a desired type relationship is expressible. It can reveal an awkward declaration or an unhelpful diagnostic. It cannot establish the performance of a compiler implementation that does not yet exist in its intended form.
The June announcement asks two practical questions about the draft. One concerns whether the resulting code feels coherent to Go programmers. The other concerns whether it solves the problems that motivated requests for generics. Both questions require attempts at real algorithms. A demonstration that syntax parses is weaker evidence than a working container whose callers retain useful type information.
The January announcement moves this work into the formal proposal process. Its request for substantive criticism follows from the size of the change. Agreement that reusable algorithms are useful leaves room for disagreement about the mechanism. A proposal discussion needs examples that expose a limitation or explain a semantic problem, since a preference alone cannot establish either.
What remains undecided
The announcement's implementation goal depends on acceptance. It aims for something complete, potentially without full optimization, by the end of the year, perhaps in Go 1.18 betas. That wording is a target for experimentation. It does not establish that the proposal has been accepted or that production code can already rely on it.
Existing programs are part of the design's constraint too. The announcement describes the proposed changes as backward compatible. That commitment matters because adding a way to express a generic algorithm should not force every existing package to adopt it. A concrete function remains a reasonable design when its behavior belongs to one concrete type.
I am most interested in the cases where a type parameter removes an artificial loss of information. A container can retain its chosen element type, and a slice algorithm can state the relationship between its input and result. The proposal gives those cases explicit declarations while retaining constraints that limit what a generic implementation may assume. Those are specific properties that experiments with the draft can now examine.