Every month in our team we do a Code as You Like Day, which is basically a day of taking time off regular work and hacking something up, learning something new or even fixing some pet-peeves in the system. This month I chose to learn about go-lang generics.
I started go many years back while coming from mainly coding in C++ and C#. Also in Adobe almost 20 years back I got a week long class on generic programming from Alexander Stepanov himself. I missed generics terribly and hated all the code I had hand role out for custom container types. So I was looking forward to generics in go.
This was also the first time I was trying to use a non-stable version of go as generics is available currently as go 1.18 Beta 2. Installing this was a bit confusing for me.
I just attempted go install which seemed to work
but seemed like it did not work. I had to do an additional step of download. That wasn't very intuitive.
For my quick test, I decided to do a port my quick and dirty stack implementation from relying on interface{} to use generic type.
I created a Stack with generic type T which is implemented over a slice of T.
var Full = errors.New("Full")
var Empty = errors.New("Empty")
type Stack[T any] struct {
arr []T
curr int
max int
}
Creating two functions to create a fixed size stack or growable was a breeze. Using the generic types was intuitive.
func NewSizedStack[T any] (size int) *Stack[T] {
s := &Stack[T]{max: size}
s.arr = make([]T, size)
return s
}
func NewStack[T any]() *Stack[T] {
return &Stack[T]{
max: math.MaxInt32,
}
}
However, I did fumble on creating the methods on that type. Because I somehow felt I need to write it as func (s *Stack[T])Length[T any]() int {}. However, the [T any] is actually not required.
func (s *Stack[T]) Length() int {
return s.curr
}
func (s *Stack[T]) IsEmpty() bool {
return s.Length() == 0
}
Push and Pop worked out as well
func (s *Stack[T]) Push(v T) error {
if s.curr == len(s.arr) {
if s.curr == s.max {
return Full
} else {
s.arr = append(s.arr, v)
}
} else {
s.arr[s.curr] = v
}
s.curr++
return nil
}
func (s *Stack[T]) Pop() (T, error) {
var noop T // 0 value
if s.Length() == 0 {
return noop, Empty
}
v := s.arr[s.curr-1]
s.arr[s.curr-1] = noop // release the reference
s.curr--
return v, nil
}
However, for pop I needed to return a nil/0-value for the generic type. It did seem odd that go does not implement something specific for it. I had to create a variable as noop and they return that.
Using the generic type is a breeze too, no more type casting!
s := NewStack[int]()
s.Push(5)
if v, e := s.Pop(); e != nil {
t.Errorf("Should get poped value")
}