/ golang

Poor man's generics in Golang (Go) [or pick your poison]

Golang does not have generics. Period.

With that in mind, I just wish to share how I am thinking of implementing generic code. For this example, I am going to create a generic Stack data-structure.

type Stack struct {
	T   reflect.Type
	arr []interface{}

Yes, it wraps around a slice of interface{} type. And yes, that means loosing static safety. I also do store the Type of values that are permitted to be added to the stack. Storing this Type is what would help us to prevent dirty data from entering our Stack and wreaking havoc later.

Next, is the constructor function for our Stack.

func New(T reflect.Type) *Stack {
	return &Stack{T: T}

Also, lets define some errors that we shall use for indicating unacceptable conditions in our Stack implementation.

var TypeMismatchError = errors.New("new value does not match the type of the stack")
var EmptyStackError = errors.New("unable to Peek/Pop and empty stack")

While the API for our Stack may contain many functions, I am going to demonstrate the two that are the most useful, namely the Pop() and Push() functions.

func (s *Stack) Pop() interface{} {
	var val interface{}
	val, s.arr = s.arr[len(s.arr)-1], s.arr[:len(s.arr)-1]
	return val

Nothing special there. It pops the last element from the arr slice and returns it as an interface{} type. This means, that we need to convert the popped value back to the original type, before we can use it in the client code.

func (s *Stack) Push(e interface{}) (*Stack, bool, error) {
	if reflect.TypeOf(e) != s.T {
		return s, false, TypeMismatchError
	s.arr = append(s.arr, e)
	return s, true, nil

The Push function is where I stop unsupported data type from entering our Stack and limit the side effects of using the interface{} type a bit. Rather than throwing a panic, we return the success/failure status as a bool and the error (TypeMismatchError in case, addition of unsupported type was attempted). By stopping dirty data from entering the Stack, we can be assured that our code wont break when converting the popped values back to their original types.

The following code demonstrates the usage of our generic Stack.

func main() {
	// create a new Stack to hold the value of type MyStruct
	st := NewStack(reflect.TypeOf(MyStruct{}))

	// push some values onto the stack
	// pop values from stack
	v := st.Pop().(MyStruct)  // need to convert the interface{} to MyStruct
	fmt.Println(v.msg)	// "again"
	v = st.Pop().(MyStruct)
	fmt.Println(v.msg)	// "world"
	// try pushing some illegal value onto stack
	var success bool
	var err error
	st, success, err = st.Push(1)	
	fmt.Println("1 pushed to stack", success)	// "1 pushed to stack false"
	fmt.Println("error returned via pushing to stack:", err)		// "error returned via pushing to stack: new value does not match the type of the stack"

The above code is available for demo here

In conclusion:

  • Golang doesnt have generics and there is nothing you can do to get true generics in Go.
  • Writing generic code involves converting the data to and from interface{} values
  • This leads to decrease in performance. For example a stack of int type was 15 times faster in my personal benchmarks. And thats a lot !
  • Using interface{} everywhere leads to the loss of the safety provided by static typing.

In short, pick your poison - Generic (like) code vs Static Safety and speed !