Go or Golang is an object-oriented programming language, published by Google in 2009. It is characterized by being compiled, its concurrency support, goroutine and channel based and, precisely the latter, makes it a popular choice when creating distributed systems.
First of all, it is necessary to make a brief note that, unlike other object-oriented programming languages, Golang does not have type inheritance or keywords (such as the word class) that denote support for this paradigm.
What are generics?
Generics, or generic programming, is a concept that became popular in the 1970s, with Ada being the language that brought it to the widest public.
When generics are applied, algorithms are used that are independent of a single data type, which is defined a posteriori. This allows the creation of functions that operate on sets of data types instead of a single one, thus reducing the amount of duplicate code.
How generics work in Golang
To understand the generics in Golang, you must first understand the constraints they have. These constraints tell the corresponding parameter which types are allowed.
type Numeric interface {
int | float64
}
func Sum[T int | float64](a, b T) T {
return a + b
}
func Sum[T Numeric](a, b T) T {
return a + b
}
For example, in this function we have a generic T
that allows only two data types: int and float64. It is also noted that the types can be declared directly or an equivalent interface can be created.
Another issue to take into account is that the chosen types must allow all operations on the generic data.
func Multiply[T int | string](a, b T) T {
return a * b
}
An example of this would be the Multiply()
function. In this case, the code would not compile since the string type cannot be multiplied.
Finally, functions are not restricted to a single generic type, as they can accept more than one.
func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
var s V
for _, x := range m {
s += x
}
return s
}
In this example you see two generics. The first one is called K
and implements the comparable interface, while the second one, V
, can be int64 or float64.
As a note, when calling a function with generics you can specify the types at call time or let the compiler infer these types.
SumIntsOrFloats[string, int64](listOfInts)
SumIntsOrFloats(listOfInts)
Limitations in Golang
Unfortunately, being a relatively new addition, Golang has a number of limitations when using generics.
- You cannot create generic methods on top of regular structs, they must be generic.
// Invalid use of generics
type CustomStruct struct{...}
func (m CustomStruct) SomeMethod[T int | float64](x T) {
...
}
// Uso válido de los genéricos
type CustomStruct[T] struct{
value T
}
func (m CustomStruct[T]) SomeMethod(x T) {
...
}
There are no variadic parameters.
- Go does not currently support variadic type parameters, which means that you cannot define a generic function or type that accepts a variable number of type arguments.
// This is not allowed
func SumIntsOrFloats[V int64 | float64](nums ...V) V {
var s V
for _, x := range nums {
s += x
}
return s
}
- Generic constants cannot be made.
- Go does not support using constants as type parameters. You cannot parameterize a type with a constant value, which could otherwise be useful in situations where you want to create types that depend on constant values (e.g., a fixed-size array type).
- Generics cannot be used in interfaces.
// This is not allowed
type GenericInterface interface {
GenericFunction[T int|float](num T) T
}
Advantages and disadvantages of Golang
Each language that allows the use of generics has its advantages and disadvantages, so in this section we will focus on analyzing them.
PROS
- Reusability and code abstraction
- Generics allow reusable code to be written independently of code typing, resulting in less duplicated code.
- Type safety
- By limiting the possible types that a function can adopt, this ensures that users do not use the wrong types later. This reduces runtime errors related to type castings that normally occur when using
interface{}
.
- By limiting the possible types that a function can adopt, this ensures that users do not use the wrong types later. This reduces runtime errors related to type castings that normally occur when using
- Better performance compared to Empty Interfaces and Reflection
- Since generics are prepared at compile time, they provide a performance improvement over the
interface{}
and reflects used in Golang, since these are performed at runtime and are not optimized.
- Since generics are prepared at compile time, they provide a performance improvement over the
CONS
- Increased complexity
- Go’s philosophy is based on simplicity and, unfortunately, generics add a layer of complexity to the code.
- Potentially larger binaries
- Since Go generates a specific version of the function for each generic, it can lead to the generation of heavier binaries. This is an aspect to keep in mind, as for more complex systems it can actually increase the size of the generated binaries.
- Learning curve
- This point can be linked to the increase in complexity. Golang, being a relatively simple language, this layer of complexity makes it difficult for novice developers to learn to use this more complex typing system.
- Restrictions
- Finally, programmers may be limited by the aforementioned constraints. These mean that certain uses of generics that are possible in other programming languages are not possible with Go generics.
Conclusion
Generics can be a very powerful tool, not only in Golang but also in many other languages. They are able to reduce the amount of duplicated code and make life easier for developers, but they also have certain disadvantages and limitations that must be considered when implementing them. Therefore, when using them, think carefully about what kind of solution you are implementing and whether it will benefit from their use.