Generics in GoLang
The release 1.18 that came out on 15 Mar 2022 introduces the long awaited generics into GoLang and is one of the most significant release in a long time.
Before Generics
Let's take the example of constructing a Set Datastructure for type int
and type string
. The code will look something
like this in <= 1.17
Set of string
package main
import "fmt"
func main() {
s := NewStringSet()
s.Add("hello")
s.Add("hello")
s.Add("world")
fmt.Println(s.Values())
}
type StringSet struct {
m map[string]struct{}
}
func NewStringSet() *StringSet {
return &StringSet{m: make(map[string]struct{})}
}
func (s *StringSet) Add(ele string) {
s.m[ele] = struct{}{}
}
func (s *StringSet) Remove(ele string) (wasRemoved bool) {
if _, ok := s.m[ele]; ok {
wasRemoved = true
delete(s.m, ele)
} else {
wasRemoved = false
}
return wasRemoved
}
func (s *StringSet) Contains(ele string) (contains bool) {
if _, ok := s.m[ele]; ok {
contains = true
} else {
contains = false
}
return contains
}
func (s *StringSet) Values() []string {
var values []string
for k, _ := range s.m {
values = append(values, k)
}
return values
}
Set of int
package main
import "fmt"
func main() {
s := NewIntSet()
s.Add(1)
s.Add(1)
s.Add(2)
fmt.Println(s.Values())
}
type IntSet struct {
m map[int]struct{}
}
func NewIntSet() *IntSet {
return &IntSet{m: make(map[int]struct{})}
}
func (s *IntSet) Add(ele int) {
s.m[ele] = struct{}{}
}
func (s *IntSet) Remove(ele int) (wasRemoved bool) {
if _, ok := s.m[ele]; ok {
wasRemoved = true
delete(s.m, ele)
} else {
wasRemoved = false
}
return wasRemoved
}
func (s *IntSet) Contains(ele int) (contains bool) {
if _, ok := s.m[ele]; ok {
contains = true
} else {
contains = false
}
return contains
}
func (s *IntSet) Values() []int {
var values []int
for k, _ := range s.m {
values = append(values, k)
}
return values
}
Even though the logic of storing elements in a set is the same because of the unavailability of generics, we are forced to write duplicate code one for int and one for string.
After Generics
package main
import (
"fmt"
"strconv"
)
type test struct {
v int
}
func (t *test) String() string {
return strconv.Itoa(t.v)
}
func main() {
s := NewSet[*test]()
s.Add(&test{v: 1})
s.Add(&test{v: 1})
fmt.Println(s.List())
s.Remove(&test{v: 1})
fmt.Println(s.List())
}
type Set[E fmt.Stringer] struct {
m map[string]E
}
func NewSet[E fmt.Stringer]() *Set[E] {
s := &Set[E]{}
s.m = make(map[string]E)
return s
}
func (s *Set[E]) Add(ele E) {
s1 := ele.String()
s.m[s1] = ele
}
func (s *Set[E]) Remove(ele E) bool {
if _, ok := s.m[ele.String()]; !ok {
return false
}
delete(s.m, ele.String())
return true
}
func (s *Set[E]) List() []E {
var list []E
for _, e := range s.m {
list = append(list, e)
}
return list
}
func (s *Set[E]) Contains(ele E) bool {
if _, ok := s.m[ele.String()]; !ok {
return false
}
return true
}
This implementation of set using generics can be used to store elements of any type, even structs, provided that the type
implements the interface fmt.Stringer
.
Generics in struct
A struct with a generic type can be declared as follows:
type TestStruct[E any] struct {
Ele E
}
The Generic is enclosed inside a square bracket pair, with generic name (E
) and the generic constraint (any
), any
simply means that any type can become a generic of this struct. You can replace this any
constraint with your own custom
type or interface.
If you want to restrict to generic constraint to two types you can use a pipe |
and add two or more types as constraints, like this
type TestStruct[E int | int64] struct {
Ele E
}
Generics in a function
A function with a generic type can be declared as follows:
func Test[E any](ele E) {
}
Add a square bracket pair after your function name and before the function parameter list.
Generics in a receiver (method)
For you to add a generic to a receiver method, the struct should first implement the generic. Like this
type TestStruct[E int | int64] struct {
Ele E
}
func (t *TestStruct[E]) TestMethod(ele E) {
}
What will be the impact of generics on golang
Golang is built on the principle of bringing the long-lost simplicity back to programming. Generics will help to write better libraries and packages, ORMs, etc...
But we will have to wait and see how standards and behaviours emerge in the community around generics.
Why did golang had to choose Square Brackets for generics though?😓
Java uses < >
angle brackets for generics, golang could have followed the same, this whole thing with square brackets
might make the general appearance of the go program a little confusing and alien looking considering arrays also use
Square Brackets.