WARNING: this post is now outdated and its code won’t work! I wrote an updated version of it.
2020 has been full of surprises. It’s been some years since the first implementation of generics in Go, and I can’t believe I wrote this title and it’s not an April Fools prank.
Yesterday an article named The Next Step for Generics was published on the Go Blog with the current draft for generics in Go. They also released a version of The Go Playground which runs this current draft.
The first thing I tried to write was an implementation of Map
that would receive a slice of strings and apply strings.ToUpper
to every element of it [play]:
package main
import (
"fmt"
"strings"
)
func Map(type T)(s []T, fn func(t T) T) []T {
ret := make([]T, len(s))
for i, input := range s {
ret[i] = fn(input)
}
return ret
}
func main() {
var (
slice = []string{"This\n", "is\n", "some\n", "string"}
changedSlice = Map(slice, strings.ToUpper)
)
fmt.Printf("%v\n", changedSlice)
}
The output is:
[THIS
IS
SOME
STRING]
What about mapping a slice of ints? Same thing, but after all these years copying functions and changing their types, I wanted to believe I could use the same Map
function to apply another function using different parameters [play].
package main
import (
"fmt"
)
func Map(type T)(s []T, fn func(t T) T) []T {
ret := make([]T, len(s))
for i, input := range s {
ret[i] = fn(input)
}
return ret
}
func main() {
var (
numSlice = []int{1, 2, 3, 4, 5, 6}
changedNumSlice = Map(numSlice, func(i int) int { return i * 2 })
)
fmt.Printf("%v\n", changedNumSlice)
}
And the output is:
[2 4 6 8 10 12]
What if I want to create a Map
function that converts a type T1
to a type T2
? In this example, we’ll convert an int
to an int64
, nothing really fancy. I also added a ForEach
implementation to print the slice [play].
package main
import (
"fmt"
)
func Map(type T1, T2)(s []T1, fn func(t T1) T2) []T2 {
ret := make([]T2, len(s))
for i, input := range s {
ret[i] = fn(input)
}
return ret
}
func ForEach(type T)(s []T, fn func(t T)) {
for _, input := range s {
fn(input)
}
}
func main() {
var (
toInt64 = func(i int) int64 { return int64(i) }
print = func(i int64) { fmt.Printf("%d is an int64\n", i) }
numSlice = []int{1, 2, 3, 4, 5, 6}
changedNumSlice = Map(numSlice, toInt64)
)
ForEach(changedNumSlice, print)
}
Again, the output:
1 is an int64
2 is an int64
3 is an int64
4 is an int64
5 is an int64
6 is an int64
Well, a long time ago I used to have dozens of implementations of Contains
to check if a slice contained some element. So here I tried to re-implement them:
package main
import (
"fmt"
)
// Spoiler: WRONG CODE!
func Contains(type T)(haystack []T, needle T) bool {
for _, current := range haystack {
if current == needle {
return true
}
}
return false
}
func main() {
numbers := []int{1, 2, 4, 8, 16, 32}
fmt.Printf("Has 1? %t\n", Contains(numbers, 1))
fmt.Printf("Has 5? %t\n", Contains(numbers, 5))
}
And… I failed miserably:
type checking failed for main prog.go2:9:6: cannot compare current == needle (operator == not defined for T)
I checked the updated design draft, and it can be easily solved using type contraints. After adding the constraint comparable
to the type contract II used, it works as expected [play]:
package main
import (
"fmt"
)
func Contains(type T comparable)(haystack []T, needle T) bool {
for _, current := range haystack {
if current == needle {
return true
}
}
return false
}
func main() {
numbers := []int{1, 2, 4, 8, 16, 32}
fmt.Printf("Has 1? %t\n", Contains(numbers, 1))
fmt.Printf("Has 5? %t\n", Contains(numbers, 5))
}
And finally the output:
Has 1? true
Has 5? false
According to the Go blog,
if everybody is completely happy with the design draft and it does not require any further adjustments, the earliest that generics could be added to Go would be the Go 1.17 release, scheduled for August 2021. In reality, of course, there may be unforeseen problems, so this is an optimistic timeline; we can’t make any definite prediction.
I’ve been waiting for years, August 2021 is fine. I only hope the covid-19 vaccine arrives before. 🙂
More examples?
Adding more examples as I find them:
- Matt Layher wrote an implementation of a hashtable using generics. (June 17)