Generics in Golang

Learn via video courses
Topics Covered

Overview

In Go, generics refer to the ability to write code that works with any type of data, rather than being specific to a certain type. This allows more flexibility and reusability in your code, as you can write functions and data structures that can be applied to different types of data.

Go Collections without Generics

Let's see an example of creating a function that finds the maximum value in a slice of integers.

Output for the above code:

Run the code!

Explanation of the code:

In this code, we are merely determining the integer array's maximum values.

Okay, so, what is the problem with this code?

Imagine if you want to get the maximum value of a slice of float, what we can do? Again, if you wanted to find the maximum value in a slice of floats, you would have to write another function, maxFloat, with the same logic but for floats.

For Example,

Output for the above code:

Run the code!

Explanation of the code:

  • In the above example, we have created the two same functions for the same work but for different data types.
  • This approach can lead to a lot of duplicated code, and make code less flexible and harder to maintain.

Can we have some other option?
Obviously, we have Generics, let's learn about them.

Go Collections with Generics

Output for the above code:

Run the code!

Explanation of the code:

  • A key type and a value type are needed for the map. The key type must always satisfy the comparable constraint, whereas the value type has no restrictions.
  • In the above example, a generic function SumIntsOrFloats that takes in a map of type map[K]V where K is a comparable type and V is either an int64 or a float64. This function uses Go's built-in range keyword to iterate through the map and adds each value to a variable s of type V. The function then returns the sum of all the values in the map.
  • In the main function, the code creates two maps, one for int64 values and another for float64 values. Then it calls the SumIntsOrFloats function passing these maps as arguments, and the function will return the sum of all the values in the maps.

Restricting Generic Types

In Go, you can restrict the types that a generic function or struct can accept by using Go's type assertion and interfaces.

One way to restrict the types that a function can accept is to create an interface that defines the methods that the types must implement.

For Example:

Output for the above code:

Run the code!

Explanation of the code:

  • In this example, the MyFunction function takes an argument of type MyStringer. By creating the MyStringer interface and requiring that the argument passed to MyFunction implements this interface, we can ensure that only types that have a String() method can be passed as an argument to MyFunction.
  • The MyStruct struct implements the MyStringer interface by having a String() method, so it is a valid argument for the function.
  • On the other hand, a string does not implement the MyStringer interface, so it will result in a compile-time error if we try to pass it as an argument to MyFunction.

Example where Generics are Less Performant:

With Genric:

Output for the above code:

Run the code

Explanation of the code:

  • In this example, we are using the generic type interface{} instead of a specific type like int. This allows the variable myVar to hold any type of value, but it will be less performant and efficient than using a specific type.
  • Additionally, using a generic type like interface{} can make the code less readable and harder to understand, as the specific type is not known at compile time.

Without Genric:

Output for the above code:

Run the code

Explanation of the above code:

  • In this example, using a specific type (int) instead of a generic type allows the code to be more performant and efficient.
  • Go's static typing means that the specific type is known at compile time, so the program can optimize the code accordingly. Additionally, using a specific type can make the code more readable and easier to understand.

Limitations of Generics

While generics can be a useful tool in certain situations, they also have some limitations in Go:

  • Limited type inference:
    Go's type inference is not as advanced as in other languages, which means that you may have to explicitly specify the types for generic functions and structs, making the code less readable.
  • Performance overhead:
    Using interfaces and type assertion can add some overhead to your code, which can affect the performance of your program. This is especially true for large and complex programs that use generics extensively.
  • No runtime type checking:
    Go does not perform any runtime type checking for generics, meaning that if you pass a type that does not match the expected type, it will not be detected until runtime, resulting in a runtime error.

It is important to be aware of these limitations and consider them when deciding whether to use generics in your code.

At last, while it's possible to use generics in Go, it requires additional work and has some limitations compared to other languages that have built-in support for generics. These limitations may make it less suitable for certain types of problems and more complex to use.

Conclusion

  • Generics is a useful tool in certain situations, such as the case with the sum method, where they can improve flexibility and code reusability.
  • However, it is important not to think of generics as a replacement for other techniques such as interfaces, but rather as an additional tool to use when necessary. It's important to use them only when they are truly needed and not overuse them just because they are available.
  • Generics can offer more flexibility and reusability in your code, but they may come with a performance overhead. In contrast, interfaces can be more efficient but less flexible. If it's possible to use an interface instead of generics, it's generally recommended to do so to maintain the code readability and performance.