神刀安全网

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 datastructure.

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     st.Push(MyStruct{"hello"})     st.Push(MyStruct{"world"})     st.Push(MyStruct{"again"})      // 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 !

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » Poor man's generics in Golang (Go) [or pick your poison]

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址