Search

Monday, February 07, 2022

Go Generics


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")
}