Codementor Events

Generics In Go Explained.

Published Jul 28, 2022
Generics In Go Explained.

Start writing here...In programming, the term generics refers to the ability in which a variable, method, function or type that would normally be for a single data type being able to be used for multiple types.

As at March 2022, Go version 1.18 now supports generics. This is a feature that a lot of Go developers had hoped will come to Go and it must have been a festive season for us when it was announced. Prior to this monumental announcement, their has been different techniques smart people had used to implement generics in Go; feel free to use other resources when it comes to getting more understanding. At the end of this post, you’d understand the fundamental techniques for implementing generics in Go.

Prerequisite

Prior experience with Go will be useful for following along with this post.

Generic Implementation

Generics in Go can be implemented on functions and types. For functions, people with prior understanding of method receivers might see a structural similarity with how generic functions are defined.

func Index[T comparable](s []T, x T) int

Figure A — Generic Function implementation

func (st SomeType) Index(pts []ParamType, pt ParamType) int

Figure B — Method receiver implementation for type SomeType

In the case of method receivers, the subject type is enclosed within brackets and comes before the method name, whereas generic functions define its subject type within square brackets and immediately after the function’s name. I have used the term “subject type” however, you’d find that it is mostly called “type parameter”.

In Figure A, the Type “T” is the generic type that fulfils the built in constraint “comparable”. This prior definition of the type parameter enables us to define function parameters that have “T” as a type. Thus, we can say that s is a slice of type T and x is a variable of type T.

Although we have defined our generic type as “T”, ideally any uppercase alphabet can be used for its representation.

The built-in comparable constraints makes it possible to use boolean equality (==) or boolean inequality (!=). The source code below is extracted from a tour of go:

package main
import “fmt”
// Index returns the index of x in s, or -1 if not found.
func Index[T comparable](s []T, x T) int {
   for i, v := range s {
       // v and x are type T, which has the comparable
       // constraint, so we can use == here.
       if v == x {
          return i
       }
    }
   
   return -1
}
func main() {
   // Index works on a slice of ints
   si := []int{10, 20, 15, -10}
   fmt.Println(Index(si, 15))
  // Index also works on a slice of strings
   ss := []string{“foo”, “bar”, “baz”}
}

Figure C — A tour of Go’s generic function example

Multiple Function Type Parameters

A generic function can also have multiple type parameters.

func Index[T comparable, K comparable](s []T, x T, y K)

Figure D.0 — Generic function with multiple type parameters

package main
import “fmt”
// Index demonstrates multiple type parameters
func Index[T comparable, K comparable](s []T, x T, y K) {
   for _, v := range s {
       fmt.Println(y)
       fmt.Println(v)
       fmt.Println(x)
       fmt.Println(x != v)
       fmt.Println(y)
   }
}
func main() {
   // Index works on a slice of ints and a third
   // parameter of type string
   si := []int{10, 20, 15, -10}
   Index(si, 15, “ — - ”)
   // Index also works on a slice of strings and a third 
   // parameter of type int
   ss := []string{“foo”, “bar”, “baz”}
   Index(ss, “hello”, 4)
}

Figure D.1 — An example of a generic function with multiple type parameters

Another built-in constraint we can use is “any”. It is mostly used when you don’t need to perform boolean equality (==) or boolean inequality (!=) operations on the generic type.

func Index[T any](s []T, x T) int

Figure E.0 — Function generics with built-in “any” constraint

Generic Implicit Types

Go also supports creating types that are generic. A type can be parameterised with a type parameter as shown below.

package main
import "fmt"
type SomeType[T any] struct {
   next *SomeType[T]
   value T
}
func main() {
   // SomeType works on a singly-linked list of integers  
   sti := &SomeType[int]{ value: 5};
   fmt.Println(sti)
   // SomeType works on a singly-linked list of strings
   sts := &SomeType[string]{value: "John Doe"}
   fmt.Println(sts)
}

Summary

In this post we have discussed function and type generics in Go. We saw how “comparable” built-in constraint provides boolean operations on types.

If this teaching technique was useful, you can share the it across your social media accounts for your friend’s benefits. To get in touch, visit https://omept.com.

Discover and read more posts from George Ndu O.
get started