Mastering Generics In Go: A Comprehensive Tutorial
Introduction
Golang generics were introduced in Go 1.18, this enables us to write generic functions and types.
NOTE: This article assumes that you have go 1.18 or later installed on your machine. If you don’t, you can download it from here.
What are Generics in Golang?
Generics are a way to write functions and types that can work with any type. In Golang generics can be defined using empty interfaces, or using type parameters and type inference.
Before the launch of go 1.18, we had to write a lot of boilerplate code to write generic functions and types. For example, let’s look at how we would write a generic function that takes a slice of any type and returns the first element of the slice.
In the above example, we defined two functions FirstInt
and FirstString
that take a slice of int
and string
respectively and return the first element of the slice. We then called the functions with two different types, int
and string
.
We could use Generic Interfaces with no constraints to achieve the same result.
In the above example, we defined a generic function First
that takes a slice of any type T
and returns the first element of the slice. We then called the function with two different types, int
and string
.
Interfaces in Golang
Before we dive into generics, let’s first understand interfaces. Interfaces are a way to define a set of methods that a type must implement. For example, the io.Writer
interface defines a set of methods that a type must implement to be a writer.
In the above example, we defined a Writer
interface that defines a Write
method. We then defined a variable w
of type Writer
and assigned it to os.Stdout
. This is possible because os.Stdout
implements the Writer
interface.
Generic Interfaces in Golang
Let’s now look at how we can write generic interfaces in Go. We will start by writing a generic interface that defines a Write
method that takes a slice of any type and returns the number of bytes written and an error.
Output:
In the above example, we defined a generic interface Writer
that defines a Write
method that takes a slice of any type T
and returns the number of bytes written and an error. We then defined a variable w
of type Writer[int]
and assigned it to os.Stdout
. This is possible because os.Stdout
implements the Writer
interface.
Type inference in Golang
Let’s now look at how we can use type inference in Go. We will start by writing a generic function that takes a slice of any type and returns the first element of the slice.
Output:
In this example, we defined a generic function First
that takes a slice of any type T
and returns the first element of the slice. We then called the function with two different types, int
and string
. The compiler can infer the type of the generic function from the type of arguments passed to the function.
In cases where a type has an underlying type, the compiler might not be able to infer the type of the generic function. For example, let’s look at how we would write a generic function that takes a slice of any type and returns the first element of the slice.
Output:
In this example, the generic function First
has a constraint T int
which means that the type of the generic function must be an int
. The compiler is not able to infer the type Int
from the argument passed to the function even though Int
has an underlying type of int
. To fix this, we can use the ~
operator to tell the compiler that Int
is an int
.
Output:
Generic Functions in Golang
Let’s now look at how we can write generic functions in Go. We will start by writing a generic function that takes a slice of any type and returns the first element of the slice.
Output:
In the above example, we defined a generic function First
that takes a slice of any type T
and returns the first element of the slice. We then called the function with two different types, int
and string
.
Generic Types in Golang
Generic types are types that can work with any type. Let’s look at an example of a generic type that implements the Writer
interface.
Output:
In the above example, we defined a generic type Writer
that implements the Writer
interface. We then defined a variable w
of type Writer[int]
and assigned it to os.Stdout
. This is possible because os.Stdout
implements the Writer
interface.
Generic Constraints in Golang
We can also add constraints to generic types and functions. Let’s look at an example of a generic function that takes a slice of any type that implements the io.Writer
interface and returns the first element of the slice.
Output:
Output:
In the above two code blocks, we introduced generic constraint to the generic function First
. In both examples, First
takes T
as a generic type that implements the io.Writer
interface. In the first example, we passed a slice of io.Writer
to the function and it worked as expected. In the second example, we passed a slice of int
and string
to the function and it failed to compile because int
and string
do not implement the io.Writer
interface.
Generic Methods in Golang
We can also define generic methods. Let’s look at an example of a generic method that takes a slice of any type and returns the first element of the slice.
In the above example, we defined a generic method First
that takes a slice of any type T
and returns the first element of the slice. We then called the method with two different types, int
and string
. The compiler can infer the type of the slice from the arguments passed to the method.
A method is a function with a special receiver argument. The receiver
appears in its argument list between the func
keyword and the
method name. In the above example, the method First
has a
receiver of type Slice[T]
. This means that the method can only
be called on a variable of type Slice[T]
.
Generic Variadic Functions in Golang
We can also define generic variadic functions. Let’s look at an example of a generic variadic function that takes a slice of any type and returns the first element of the slice.
A variadic function is a function that takes a variable number of arguments.
In Go, a variadic function is defined by adding an ellipsis (…) after the
type of the last parameter. For example, the function First
in
the above example is a variadic function because it takes a variable number
of arguments of type T
.
In the example above, we defined a generic variadic function First
that takes a slice of any type T
and returns the first element of the slice. We then called the function with two different types, int
and string
.
Generic Variadic Methods in Golang
We can also define generic variadic methods. Let’s look at an example of a generic variadic method that takes a slice of any type and returns the first element of the slice.
In the above example, we defined a generic variadic method First
that takes a slice of any type T
and returns the first element of the slice. We then called the method with two different types, int
and string
. The compiler can infer the type of the slice from the arguments passed to the method.
Generic Variadic Types in Golang
We can also define generic variadic types. Let’s look at an example of a generic variadic type that implements the Writer
interface.
In the above example, we defined a generic variadic type Writer
that implements the Writer
interface. We then defined a variable w
of type Writer[int]
and assigned it to os.Stdout
. This is possible because os.Stdout
implements the Writer
interface. We then called the Write
method on the variable w
and passed a slice of bytes to it.
Generic Variadic Constraints in Golang
We can also add constraints to generic variadic types and functions. Let’s look at an example of a generic variadic function that takes a slice of any type that implements the io.Writer
interface and returns the first element of the slice.
Here function First
takes a slice of any type T
that implements the io.Writer
interface and returns the first element of the slice. We then called the function with two different types, os.Stdout
and os.Stderr
. It worked as expected because both os.Stdout
and os.Stderr
implement the io.Writer
interface.
Generic Type Sets in Golang
Now that we have looked at how generics allow us to define both with constraints and without constraints, we realize that these are two complete extremes. You may ask yourself, what if I want to define a generic type that can only be used with a subset of types? For example, what if I want to define a generic type that can only be used with types that implement the int
and string
interfaces? This is where type sets come in.
Type sets are a set of types that can be used in a generic type, function, method, or variadic function or method. Let’s look at an example of a generic type that can only be used with types that implement the int
and string
interfaces.
Output:
If we introduced a foreign type to the function implementation, the compiler would throw an error.
Conclusion
Golang is a statically typed language. This means that the type of a variable is known at compile time. In this article, we looked at how to define generic types, functions, methods, and variadic functions and methods in Golang. We also looked at how to add constraints to generic types, functions, methods, and variadic functions and methods.