GeezerVisonThe World Wide Web, restored to 800 pixels of glory. |
| Home|About|Gaming cabinet|Archives|How it works|Starter sites |
GeezerVison is showing go.dev — An Introduction To Generics - The Go Programming Language The Go BlogAn Introduction To Generics
Robert Griesemer and Ian Lance Taylor IntroductionThis blog post is based on our talk at GopherCon 2021: The Go 1.18 release adds support for generics. Generics are the biggest change we’ve made to Go since the first open source release. In this article we’ll introduce the new language features. We won’t try to cover all the details, but we will hit all the important points. For a more detailed and much longer description, including many examples, see the proposal document. For a more precise description of the language changes, see the updated language spec. (Note that the actual 1.18 implementation imposes some restrictions on what the proposal document permits; the spec should be accurate. Future releases may lift some of the restrictions.) Generics are a way of writing code that is independent of the specific types being used. Functions and types may now be written to use any of a set of types. Generics add three new big things to the language:
Type ParametersFunctions and types are now permitted to have type parameters. A type parameter list looks like an ordinary parameter list, except that it uses square brackets instead of parentheses. To show how this works, let’s start with the basic non-generic
We can make this function generic–make it work for different
types–by adding a type parameter list.
In this example we add a type parameter list with a single type
parameter
It is now possible to call this function with a type argument by writing a call like
Providing the type argument to After successful instantiation we have a non-generic function that can be called just like any other function. For example, in code like
the instantiation Type parameters can be used with types also.
Here the generic type Type setsLet’s look a bit deeper at the type arguments that can be used to instantiate a type parameter. An ordinary function has a type for each value parameter; that type
defines a set of values.
For instance, if we have a Similarly, type parameter lists have a type for each type parameter. Because a type parameter is itself a type, the types of type parameters define sets of types. This meta-type is called a type constraint. In the generic In Go, type constraints must be interfaces.
That is, an interface type can be used as a value type, and it can
also be used as a meta-type.
Interfaces define methods, so obviously we can express type
constraints that require certain methods to be present.
But To make this work, we look at interfaces in a new way. Until recently, the Go spec said that an interface defines a method set, which is roughly the set of methods enumerated in the interface. Any type that implements all those methods implements that interface.
But another way of looking at this is to say that the interface defines a set of types, namely the types that implement those methods. From this perspective, any type that is an element of the interface’s type set implements the interface.
The two views lead to the same outcome: For each set of methods we can imagine the corresponding set of types that implement those methods, and that is the set of types defined by the interface. For our purposes, though, the type set view has an advantage over the method set view: we can explicitly add types to the set, and thus control the type set in new ways. We have extended the syntax for interface types to make this work.
For instance,
Another way of saying this is that this interface is satisfied by
only Now let’s look at the actual definition of
This declaration says that the For type constraints we usually don’t care about a specific type, such
as Of course we still want to specify methods in interfaces, and we want to be backward compatible. In Go 1.18 an interface may contain methods and embedded interfaces just as before, but it may also embed non-interface types, unions, and sets of underlying types. When used as a type constraint, the type set defined by an interface
specifies exactly the types that are permitted as type arguments for
the respective type parameter.
Within a generic function body, if the type of a operand is a type
parameter Interfaces used as constraints may be given names (such as
Here Because this is a common case, the enclosing
Because the empty interface is common in type parameter lists, and in
ordinary Go code for that matter, Go 1.18 introduces a new predeclared
identifier
Interfaces as type sets is a powerful new mechanism and is key to making type constraints work in Go. For now, interfaces that use the new syntactic forms may only be used as constraints. But it’s not hard to imagine how explicitly type-constrained interfaces might be useful in general. Type inferenceThe last new major language feature is type inference. In some ways this is the most complicated change to the language, but it is important because it lets people use a natural style when writing code that calls generic functions. Function argument type inferenceWith type parameters comes the need to pass type arguments, which can
make for verbose code.
Going back to our generic
the type parameter
In many cases the compiler can infer the type argument for
This works by matching the types of the arguments This kind of inference, which infers the type arguments from the types of the arguments to the function, is called function argument type inference. Function argument type inference only works for type parameters that
are used in the function parameters, not for type parameters used only
in function results or only in the function body.
For example, it does not apply to functions like Constraint type inferenceThe language supports another kind of type inference, constraint type inference. To describe this, let’s start with this example of scaling a slice of integers:
This is a generic function that works for a slice of any integer type. Now suppose that we have a multi-dimensional
Sometimes we want to scale a
Unfortunately this does not compile, failing with an error like
The problem is that the In order to fix this, we have to change the
We’ve introduced a new type parameter The new function acts the same as before if we call it with a plain
slice, but if we call it with the type But it’s fair to ask: why is it OK to write the call to Constraint type inference deduces type arguments from type parameter constraints. It is used when one type parameter has a constraint defined in terms of another type parameter. When the type argument of one of those type parameters is known, the constraint is used to infer the type argument of the other. The usual case where this applies is when one constraint uses the form
This was just an introduction to constraint type inference. For full details see the proposal document or the language spec. Type inference in practiceThe exact details of how type inference works are complicated, but using it is not: type inference either succeeds or fails. If it succeeds, type arguments can be omitted, and calling generic functions looks no different than calling ordinary functions. If type inference fails, the compiler will give an error message, and in those cases we can just provide the necessary type arguments. In adding type inference to the language we’ve tried to strike a balance between inference power and complexity. We want to ensure that when the compiler infers types, those types are never surprising. We’ve tried to be careful to err on the side of failing to infer a type rather than on the side of inferring the wrong type. We probably have not gotten it entirely right, and we may continue to refine it in future releases. The effect will be that more programs can be written without explicit type arguments. Programs that don’t need type arguments today won’t need them tomorrow either. ConclusionGenerics are a big new language feature in 1.18. These new language changes required a large amount of new code that has not had significant testing in production settings. That will only happen as more people write and use generic code. We believe that this feature is well implemented and high quality. However, unlike most aspects of Go, we can’t back up that belief with real world experience. Therefore, while we encourage the use of generics where it makes sense, please use appropriate caution when deploying generic code in production. That caution aside, we’re excited to have generics available, and we hope that they will make Go programmers more productive.
Next article: How Go Mitigates Supply Chain Attacks |
| You are visitor 01402503 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |