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.