There are many ways to implement generics. The generic implementation method introduced in Go 1.18 is implemented through type parameters.
In previous Go versions, generics have always been a pain point and flaw in Go due to the limitations of the language itself. Programmers usually use interfaces and type assertions to implement generics, but this approach can sometimes make the code difficult to understand, debug, and maintain.
To solve this problem, Go version 1.18 introduced a generic mechanism, allowing the use of similar type parameters to write generic code.
Through generics, common data structures and algorithms in the Go language, such as containers, collections, sorting, searches, etc., can be written into general code while keeping the code concise and readability.
This not only improves the reusability and maintainability of the code, but also reduces the workload of programmers and improves coding efficiency.
It should be noted that although generics are very useful programming features, they will also bring additional overhead, such as type checking at compile time and type conversion at runtime.
Therefore, when introducing generic mechanisms, Go also needs to weigh the balance between its performance overhead and code maintainability to achieve optimal programming efficiency and code quality.
1. What is a generic?
Generics are a general programming model that allows programmers to write more versatile and flexible code while reducing duplicate code and improving code readability. In programming, generics are a mechanism used to deal with types, which allows the code to complete some operations without knowing the specific type, improving the reusability and maintainability of the code.
2. Which efficiency can generics provide?
The use of generics can provide many aspects of efficiency, including:
- Reduce code redundancy. Generics can abstract some common algorithms and data structures into different types, thus reducing the redundancy of the code.
- Improve the readability of the code. Generics can make the code more concise, easy to understand and maintain.
- Improve the reusability of the code. Through generics, more general code can be written and reused in different occasions, improving the reusability of the code.
- Improve coding efficiency. Using generics allows programmers to write code more efficiently, reducing a lot of repetitive work.
3. How to use generics in Go?
In previous Go versions, generics have always been a pain point and flaw in Go due to the limitations of the language itself. Programmers usually use interfaces and type assertions to implement generics, but this approach can sometimes make the code difficult to understand, debug, and maintain. To solve this problem, Go version 1.18 introduced a generic mechanism, allowing the use of similar type parameters to write generic code.
In Go, generics are implemented using type parameters. In a function or method, a type parameter can be defined to represent the specific type to be processed. For example:
func Swap[T any](a, b *T) { tmp := *a *a = *b *b = tmp }
In this example, we define a type parameter named T, using any keyword to limit its type. Inside the function, we can use T to represent any type of variable, thus implementing a general function that exchanges the values of two variables. When calling this function, any type of variable can be passed in.
In addition to functions, generics can also be used in structures, interfaces, and methods. For example:
type Stack[T any] struct { data []T } func (s *Stack[T]) Push(x T) { = append(, x) } func (s *Stack[T]) Pop() T { x := [len()-1] = [:len()-1] return x }
In this example, we define a structure called Stack, using the type parameter T. This structure represents a common
4. Some special usage scenarios of generics
In addition to the above basic usage methods, generics also have some special usage scenarios, and the ability of generics will be more fully utilized in these scenarios.
(1) Type constraint
In some cases, we need to constrain types, allowing only certain types as generic type parameters, and type constraints can be used. In Go language, type constraints can be implemented using interfaces, such as:
type Stringer interface { String() string } func ToString[T Stringer](slice []T) []string { result := make([]string, len(slice)) for i, val := range slice { result[i] = () } return result }
In the above example, a Stringer interface is defined, which restricts that only the type that implements the interface can be used as a type parameter of the ToString function. This can avoid some types that do not support String methods.
(2) Function type as generic type parameter
In some cases, we need to use the function type as a generic type parameter, and we can use the function type as a type parameter. for example:
func Map[T any, R any](slice []T, f func(T) R) []R { result := make([]R, len(slice)) for i, val := range slice { result[i] = f(val) } return result }
In the above example, the function type is used as the type parameter, so that the passed function parameter f can be called inside the function.
(3) Custom generic types
Sometimes, the generic types provided in the standard library cannot meet our needs, and can be implemented by customizing the generic types. Custom generic types can be implemented through structures, interfaces, etc. for example:
type Stack[T any] struct { data []T } func (s *Stack[T]) Push(val T) { = append(, val) } func (s *Stack[T]) Pop() T { if len() == 0 { panic("stack is empty") } val := [len()-1] = [:len()-1] return val }
In the example above, a Stack type is defined, supporting any type of elements. By adding any keyword to the type parameter position, the definition of generic types is realized.
In short, generics are a very powerful programming technology that can improve the reusability and readability of your code. Although Go is not as good as other languages in terms of generics, with the continuous update and improvement of the Go version, the support for generics is also constantly increasing. I believe that in the near future, the generics of Go will be more perfect and powerful.
Other ways to implement generics
In Go, there is no generic syntax natively supported before 1.18, but some techniques can be used to implement generic-like functions. Here are several commonly used methods.
Below are some examples of using generics in Go, where one or more of the three methods described above are used.
(1) Use interface{} as type parameter
In this example, we implement a function Find that can find the location of a specified element in a slice. This function uses the interface{} type as a generic type parameter, can accept slices and elements of any type, and uses the T type operator == for comparison operations.
func Find[T comparable](arr []T, x T) int { for i, val := range arr { if val == x { return i } } return -1 } func main() { arr1 := []int{1, 2, 3, 4, 5} arr2 := []string{"a", "b", "c", "d", "e"} (Find(arr1, 3)) // output: 2 (Find(arr2, "d")) // output: 3 }
(2) Use reflection to implement generics
In this example, we implement a function ToString, which converts a slice of any type into a slice of string type. This function uses the interface{} type as a generic type parameter, then uses the reflection mechanism to obtain the value and type information of the slice, and uses type conversion and type assertion to complete the type conversion and operation.
func ToString(slice interface{}) []string { v := (slice) if () != { panic("not a slice") } result := make([]string, ()) for i := 0; i < (); i++ { result[i] = ("%v", (i)) } return result } func main() { arr1 := []int{1, 2, 3, 4, 5} arr2 := []string{"a", "b", "c", "d", "e"} (ToString(arr1)) // output: [1 2 3 4 5] (ToString(arr2)) // output: [a b c d e] }
(3) Use code generation tools to implement generics
In this example, we use the go generate tool to generate a code that generates an array of specified size based on the specified type. First, we write a template code that contains the code we want to generate. Then, we use the go generate command and a type parameter to generate the code. Finally, we can use the generated code to create an array of specified size.
//go:generate go run gen_array.go T=int N=10 //go:generate go run gen_array.go T=string N=5 package main import "fmt" func main() { arr1 := make([]int, 10) arr2 := make([]string, 5) (len(arr1), len(arr2)) // output: 10 5 }
In the example above, we define a template code gen_array.go, which contains a structure named Array and a function named NewArray to generate the specified size
In addition to using generic functions and generic interfaces, there are some other generic usage scenarios in Go, such as generic containers and type parameterization.
Generic containers refer to a type of container that can store any type of data. In traditional non-generic languages, it is necessary to define a corresponding container for each data type, and generic containers can implement the same container for different data types through parameterized types. In Go, you can use the interface{} type to implement generic containers. For example, the following code defines a Stack structure that can be used to store any type of data:
type Stack struct { elems []interface{} } func (s *Stack) Push(elem interface{}) { = append(, elem) } func (s *Stack) Pop() interface{} { if len() == 0 { return nil } elem := [len()-1] = [:len()-1] return elem }
In the above code, the elems field in the Stack structure is a slice of type interface{} that can store any type of data. The Push method and the Pop method can be used to add elements and pop elements to Stack respectively.
Type parameterization refers to a technique that parameterizes types. In Go language, you can use the type keyword to define a type alias, and then use this type alias as a parameter in a function or interface to implement type parameterization. For example, the following code defines a generic function Max that can compare values of any type and return the maximum:
type Ordering int const ( Less Ordering = iota - 1 Equal Greater ) func Max[T comparable](a, b T) T { switch a := compare(a, b); a { case Greater: return a } return b } func compare[T comparable](a, b T) Ordering { switch { case a < b: return Less case a > b: return Greater } return Equal }
In the above code, the Max function uses the type parameter T and uses the comparable keyword in the parameter list to limit T to be a comparable type. In the function body, another generic function compare is called to compare the sizes of a and b, and returns the maximum value based on the comparison result.
Through generic containers and type parameterization, generics in Go can achieve more flexible and general programming, improving code reusability and maintainability. However, it should be noted that generics in Go do not support implicit type conversion and dynamic types, and developers need to do type checking and type conversion when writing code.
This is the end of this article about the understanding and use of Go generics. For more information about understanding and use of Go generics, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!