Go2 Sneak Preview: Generics

TLDR;

Golang genetics is being planned for Go2. Here’s a sneak preview!

Background

A generic method is a function which objects of various types may be input as its argument. Below is an example of a Java generic method:

public static < E > void printArray( E[] inputArray ) {
// Display array elements
for(E element : inputArray) {
System.out.printf("%s ", element);
}
}

Above, the method accepts arrays of any type, be it int, string, bool etc etc…

Currently Go does not allow for generics. However, earlier this month the developers of go have released an updated design draft and an experimentation tool on playground.

Below, I’ve written a couple of examples in the playground adapted from this twitter post, that showcases the new functionality in store when generics finally got released!

Examples

1. Simple generic example

Like the Java example above, the Print method below takes a slice of generic type and prints it out.

type User struct{
UserID int
Email string
}
func Print(type T)(s []T) {
for _, v := range s {
fmt.Printf("%T, %v \n",v,v)
}
func main() {
Print([]string{"Hello, ", "playground\n"})
Print([]int{1,2,3})
Print([]bool{true, false, true})
Print([]User{
{UserID : 1, Email: "alice@shopee.com"},
{UserID : 2, Email: "bob@shopee.com"},
})
}

https://go2goplay.golang.org/p/gplnV1mtkrs

  • Take note of the new function signature of Print. T is called the type parameter.

2. A more useful generic example

Maybe the print example was a little trivial. Below is an example generic Filter method that filters a slice based on the input filter function.

func filter(type T)(s []T, f func(T) bool)[]T {
var res []T
for _, v := range s {
if f(v){
res = append(res,v)
}
}
return res
}
func main() {
isOdd := func(n int) bool{
return n%2 == 1
}
isEven := func(n int) bool{
return n%2 == 0
}
nums := []int{1,2,3,4,5,6}
fmt.Println(filter(nums,isOdd))
fmt.Println(filter(nums,isEven))
}

https://go2goplay.golang.org/p/00PeoXo3xhs

This example illustrates the best argument for using generics — abstraction of type away from implementation. For every permutation of (type, filtering algorithm), you may call the filter method and it will work as expected. Think isPalindrome for a string, isPositive for any int or any customised filter for any private struct you may instantiate!

3. Type Constraints

If you were asking yourself in the previous example, so what happens if you input a []string and isOdd into the filter method? It will cause a panic! To prevent this, we can place restrictions on the input type as follow:

type number interface{
type int, int8, int16, int32, int64
}
func getEven(type T number)(s []T)[]T {
var res []T
for _, v := range s {
if v%2 == 0{
res = append(res, v)
}
}
return res
}
func main() {
nums := []int{1,2,3,4,5,6}
fmt.Println(getEven(nums))
}

https://go2goplay.golang.org/p/9mP9Dx4R-Is

By restricting the type argument to ints, we allow the compiler to catch wrongly typed calls to our getEven Function, preventing a runtime error.

4. What about custom types?

Let’s say you have a custom type such as type customInt int, will it fulfil the type constraints?

type number interface{
type int, int8, int16, int32, int64
}
type customInt int64func getEven(type T number)(s []T)[]T {
var res []T
for _, v := range s {
if v%2 == 0{
res = append(res, v)
}
}
return res
}
func main() {
nums := []customInt{1,2,3,4,5,6}
fmt.Println(getEven(nums))
}

https://go2goplay.golang.org/p/spSHMPEcv4E

The answer is YES!

5. Interface Constraints i.e. Stringer

Not only can you out constraints on the type, you may also constraint it by interfaces:

type User struct{
UserID int
Email string
}
func (u User) String() string {
return strconv.Itoa(u.UserID)+":"+u.Email
}
func stringify(type T fmt.Stringer)(v T)string {
return v.String()
}
func main() {
user := User{
UserID: 1,
Email: "alice@shopee.com",
}
fmt.Println(stringify(user))
}

https://go2goplay.golang.org/p/5dTjhc5yQM_U

Recall the fmt.Stringer interface:

type Stringer interface {
String() string
}

In the above example, we constraint the input for stringify to all types which implements String(). However, this is kind of similar to constraining the argument directly and I am still unsure of its benefits?

edit: Found the answer from an expert — Implementing the Interface Constraint on the function allows you to pass in a Slice or a Map with a Type that fulfils the constraint.

1) func stringify(type T fmt.Stringer)(v []T)string
2) func stringify(v fmt.Stringer)string

i.e. in signature (1) we only need to implement String() to be able to pass it into the function, whereas in signature (2), we also need to ensure that the slice of types also implements String() which is a little inconvenient

6. Comparable

Comparable is a predeclared type constraint. The comparable type permits the use of == and != on the type object.

func Index(type 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() {
fmt.Println(Index([]int{1,2},2))
}

https://go2goplay.golang.org/p/ZRraCRpfk3o

7. Generic types

There are also generic types. A custom type that can be used by multiple types.

type User struct{
UserID int
Email string
}
type Vector(type T) []Tfunc (v *Vector(T)) push(element T) {
*v = append(*v, element)
}
func main() {
var intVec Vector(int)
for _,e := range []int{1,2,3} {
intVec.push(e)
}
fmt.Println(intVec) users := []User{
{UserID: 1, Email: "alice@shopee.com"},
{UserID: 2, Email: "bob@shopee.com"},
}
var userVec Vector(User)
for _,e := range users {
userVec.push(e)
}
fmt.Println(userVec)
}

https://go2goplay.golang.org/p/NJOaNjz_7kj

Take note of the way we instantiate intVec

var intVec Vector(int)

We are creating a Vector of type int. Likewise, you can also create Vectors of bool type, string type etc…

Final notes

  • Generics is still undergoing review. May still take awhile for such functionality to be live (probably in go2)
  • I barely skimmed through the design doc. Do add on to the page if you have other good examples!
  • Also feel free to point out any mistakes I may have made above! It will be much appreciated

If you want to find out more check out this comprehensive repo by ArdanLabs: https://github.com/ardanlabs/gotraining/tree/3488830f299d43bc93d5110ce82233723e916dd5/topics/go/generics

Core Server Software Engineer @ Shopee