Introduction
In the Go programming language with the version of 1.18 or later there is a new feature called Generics. Generics are a feature that has been featured in many programming languages like Java. Generics are a feature to define a type or data type as a parameter in a function or a struct
.
Using Generics in Function
The generics mechanism can be applied in a function. To use a generics feature, the any
keyword can be used to define all data types that can be used as a parameter in a function or a struct
. The other keyword that can be used is comparable
which means the data type that implements comparable
like int
and float64
.
This is an example of generics usage in a function. Before the generics are used, two functions are created called compareInt
and compareFloat
to compare two numbers for different data types including int
and float64
.
// compare two numbers for int data type
func compareInt(x, y int) bool {
return x == y
}
// compare two numbers for float64 data type
func compareFloat(x, y float64) bool {
return x == y
}
This is the example of generics usage in function to compare two numbers.
func compare[T comparable](x, y T) bool {
return x == y
}
Based on the code above, the generics are implemented in [T comparable]
syntax. The T
is a variable name for the generic parameter. The variable name for the generic parameter can contain many characters like numType
but the parameter name in general only contains a single character. The comparable
syntax means a function can be used by a data type that implements comparable
like int
and float64
.
This is an example of a function call that implements the generics.
package main
import "fmt"
func main() {
fmt.Println("compare result for int: ", compare(2, 3))
fmt.Println("compare result for float64: ", compare(2.9, 2.9))
}
Output.
compare result for int: false
compare result for float64: true
The generics can be used to define the required data types for the function with the |
operator. This is the example of generics usage in the average()
function.
// calculate the average
// T is an int or float64 data type
func average[T int | float64](nums []T) T {
var sum T = 0
for _, num := range nums {
sum += num
}
var result T = sum / T(len(nums))
return result
}
Based on the code above, the usage of the generic can be seen in [T int | float64]
syntax which means the function only can be used by the int
or float64
data type.
This is the example of the average()
function call.
package main
import "fmt"
func main() {
var intData []int = []int{1, 2, 3, 4}
var floatData []float64 = []float64{2.5, 3.4, 6.7, 2.9}
fmt.Println("average result for int data type: ", average(intData))
fmt.Println("average result for float64 data type:", average(floatData))
}
Output.
average result for int data type: 2
average result for float64 data type: 3.8750000000000004
Using Generics in Struct
The generics can be applied in a struct
. The generics usage is similar in a function by using certain keywords like any
, and comparable
and defining the required data types with the |
operator.
This is the example of a struct
without generics implementation.
type IntBox struct {
Content int
}
type StringBox struct {
Content string
}
This is the example of generics usage in a struct
called Box
.
type Box[T any] struct {
Content T
}
Based on the code above, the usage of the generic can be seen in [T any]
syntax which means the struct
can be used with any data type.
This is an example of struct
usage that implements generics.
package main
import "fmt"
type Box[T any] struct {
Content T
}
func main() {
var numberBox Box[int] = Box[int]{
Content: 200,
}
var stringBox Box[string] = Box[string]{
Content: "my content",
}
fmt.Println(numberBox.Content)
fmt.Println(stringBox.Content)
}
Output.
200
my content
This is another example of generics usage in a struct
with two generic parameters.
type Pair[K, V any] struct {
Key K
Value V
}
The number of the generic parameter is not restricted. In general, the number of the generic parameter is one or two parameters.
This is an example of struct
usage that used two generic parameters.
package main
import "fmt"
type Pair[K, V any] struct {
Key K
Value V
}
func main() {
var pairOne Pair[string, int] = Pair[string, int]{
Key: "score",
Value: 2000,
}
var pairTwo Pair[int, string] = Pair[int, string]{
Key: 10,
Value: "a value",
}
fmt.Println(pairOne.Key, pairOne.Value)
fmt.Println(pairTwo.Key, pairTwo.Value)
}
Output
score 2000
10 a value
Using Generics in Interface
The generics can be used with the interface. This is an example of generics usage in an interface.
// Number is an int or float64 data type
type Number interface {
int | float64
}
Based on the code above, the interface called Number
indicates a certain data type that has an int
or float64
data type.
The interface that uses the generics can be applied in a function or a struct
. This is an example of generic interface usage in a function.
// calculate average
// using Number interface as a type
func average[T Number](nums []T) T {
var sum T = 0
for _, num := range nums {
sum += num
}
var result T = sum / T(len(nums))
return result
}
Based on the code above, the Number
interface is used inside the average()
function which means the average
function can be called or used by a data type that implements the Number
interface including int
or float64
data type. With the interface, the code is more readable and well structured.
Notes
- The generics can be used to reduce code duplication if the code has a similar logic structure. For example, a single generic function to calculate an average can be created instead of creating many average functions for many data types.
// without generics, each average function is created for each data type
func averageInt(nums []int) int {
var sum int = 0
for _, num := range nums {
sum += num
}
var result int = sum / len(nums)
return result
}
func averageFloat(nums []float64) float64 {
var sum float64 = 0
for _, num := range nums {
sum += num
}
var result float64 = sum / float64(len(nums))
return result
}
// with generics, the average function can be used for int and float64 data type
func average[T int | float64](nums []T) T {
var sum T = 0
for _, num := range nums {
sum += num
}
var result T = sum / T(len(nums))
return result
}
Sources
I hope this article is helpful to learn generics in Go. If you have any thoughts, you can write it in the discussion section below.
Top comments (4)
Nice article, learned some new stuff.
Thank you
Nice article.
Thank you