In this blog, we’ll take a look at Golang generics, exploring what they are, why they’re useful, and how to implement them in your Go projects.

A Guide to Golang Generics


What Are Generics?

Generics allow you to write functions and data structures that can work with any data type. Instead of specifying a concrete type, you use type parameters, making your code more flexible and reusable.

Here’s an example. Here’s a function written in a typical way:

func AddInt(a, b int) int {
    return a + b
}

and with generics:

func Add[T any](a, b T) T {
    return a + b
}

In the generic version, [T any] introduces a type parameter T that can be any type - it could be int64, float, int or even string.

Why Use Generics in Go?

  • Code Reusability: Write functions and data structures that are not limited to a specific type.
  • Type Safety: Catch type-related errors at compile-time rather than at runtime.
  • Performance: Avoid the overhead of type assertions and conversions.

Understanding Type Parameters and Constraints

Type parameters are defined using square brackets [] after the function name. Constraints specify what types are acceptable for a type parameter. Here’s an example:

func Add[T int | float64](a, b T) T {
    return a + b
}

In this example, T can be either int or float64. This solves a problem in our first example where T could also be a string which is not really what we wanted.

The Any Constraint

The any constraint is an alias for interface{} and means that T can be any type. It can be useful for things like below:

func PrintValue[T any](value T) {
    fmt.Println(value)
}

Some Useful Generic Uses in Go

Swapping Two Values

func Swap[T any](a, b T) (T, T) {
    return b, a
}

usage:

x, y := Swap[int](1, 2)

Generic Stack

type Stack[T any] struct {
    elements []T
}

func (s *Stack[T]) Push(value T) {
    s.elements = append(s.elements, value)
}

func (s *Stack[T]) Pop() T {
    length := len(s.elements)
    if length == 0 {
        var zero T
        return zero
    }
    value := s.elements[length-1]
    s.elements = s.elements[:length-1]
    return value
}

usage:

intStack := Stack[int]{}
intStack.Push(10)
intStack.Push(20)
fmt.Println(intStack.Pop()) // Outputs: 20

Tips and Best Practices for Generics

  • Be Specific with Constraints: Use constraints to limit type parameters to the types you need.
  • Avoid Overcomplicating: Generics can make code complex; use them when they genuinely add value. In most cases, you probably don’t need them.
  • Testing: Write tests to ensure your generic code works with various types.

Common Mistakes with Generics

  • Runtime Type Issues: Be cautious with operations that depend on the runtime type.
  • Method Sets: Type parameters do not inherit methods from their underlying types unless specified by constraints.

Wrapping up on Generics

We hope you have found this blog post on Generics useful. Take a look at the official Go docs if you want to learn more.