Care to share? ... Linkedin Reddit

One of the most hotly debated topics in the world of the Go programming language, is the lack of generics. Generics are considered a key feature in other statically typed programming languages like Java or C#. However, the makers of Go resisted the demand to add it to the language so far. A key reason why Go’s makers and developers could utilize the language in complex production software without the presence of generics, is because of the “empty interface” feature, which helped fill some of the gaps left by the lack of generics in Go.

After going through the feedback that I received for the article, I just wanted to clarify that the article doesn’t cover every single user case for generics. The purpose of the article is rather to give the reader an idea of why people like generics, and what Go can offer instead.

It is also recommended to only use empty interfaces when you have no other choice. Otherwise, you can create your own interface that is supported by the types you are expected to use. An example of an unavoidable situation where you have to use the empty interface is the Print functions in the ‘fmt’ package, or the Json marshaller in the ‘encoding/json’ package, where you have no idea what types will come your way.

Generics, an overview

First things first, let’s start this correctly: What exactly are generics and why are they important?

The governing principle behind generic programming is that you write your code with ‘types to be specified later’. This basically means that you don’t specify your object types while writing your code, but instead you use ‘templates’ or ‘placeholders’ to be later replaced with your types. At compile time, the compiler can perform proper type checks.

Let’s explore an example in Java where we make use of generics. We’ll write a class called genericsdemo, which will host two methods: storeValue and printStoredValue.

The storeValue method will take a value of ANY type, then store it in a variable. The value could be an integer, a string, a custom object, whatever your heart pleases. Whereas the printStoredValue method will print the stored value.

Here is how the code will look like:

public class genericsdemo<T> { T value; public void storeValue(T v){ value = v; } public void printStoredValue(){ System.out.println(value); } } 1 2 3 4 5 6 7 8 9 10 11 public class genericsdemo < T > { T value ; public void storeValue ( T v ) { value = v ; } public void printStoredValue ( ) { System . out . println ( value ) ; } }

In the above code we use generics to allow the class to accept a value of any type. The key here is to use the <T> syntax in our class declaration. This allows us to then use T as the placeholder for our still unknown type.

Now let’s look into how we would use the genericsdemo class:

genericsdemo<Double> gd_double = new genericsdemo(); gd_double.storeValue(44.5); gd_double.printStoredValue(); genericsdemo<String> gd_string = new genericsdemo(); gd_string.storeValue("Hello"); gd_string.printStoredValue(); 1 2 3 4 5 6 7 genericsdemo < Double > gd_double = new genericsdemo ( ) ; gd_double . storeValue ( 44.5 ) ; gd_double . printStoredValue ( ) ; genericsdemo < String > gd_string = new genericsdemo ( ) ; gd_string . storeValue ( "Hello" ) ; gd_string . printStoredValue ( ) ;

In the above code, we first create an instance of the genericsdemo class, where we specify the data type of <T> to be a double. Then, we store a value of 44.5. At the end, we print the stored value.

We then repeat the same logic, except this time we instantiate the genericsdemo class with a String type instead of a double.

If I run the code, I get an output that looks like this:

So obviously our code accepted both data types (float and string) perfectly fine without complaining, and here -my dear reader- is where the power of generics lies for statically typed languages.

You can see how much power and flexibility generics can offer you as a developer. It is extremely popular with SDKs and API libraries, as it allows you to write a single piece of code that can support multiple different types in one go.

Generics vs Go

Now let’s move back to the Go language. Go does not support generics in any official capacity. This means that the lovely piece of Java code we covered above has no parallels in the Go language. But then what to do when we need to write a piece of code that is expected to support different data types at the same time? This is where the concept of the empty interface comes in. Unfortunately, it doesn’t take us all the way there, however it offers a nice ‘middle of the road’ solution to the problems that generics in other languages attempt to solve.

One very important remark to note though, is that we shouldn’t rely on the empty interface on every single situation. Overusing the empty interface can result of unreadable or unpredictable code on the long run. However, using it with just the right dosage should be perfectly fine. So make sure you keep that in mind whenever you consider writing a new piece of code with the empty interface.

What is the empty interface?

So what exactly is the empty interface in the Go programming language? It is exactly what the name says: it’s an empty interface:

interface{} 1 interface { }

But why is it special? The answer rests on the fact that in the Go programming language, implementing an interface is implicit and not explicit. In other words, there is no implements keyword like in Java to connect an interface with a type that implements it. Instead, Go will look for the types in your code that implement the same methods as the interface, then implicitly connect those types to the said interface.

For example, if I wrote an interface that looks like this:

type myinterface interface { method1() } 1 2 3 type myinterface interface { method1 ( ) }

Then, I wrote a struct type that looks like this:

type mystruct struct{} func (ms mystruct) method1() { } func (ms mystruct) anothermethod() { } 1 2 3 4 5 6 7 type mystruct struct { } func ( ms mystruct ) method1 ( ) { } func ( ms mystruct ) anothermethod ( ) { }

The mystruct type becomes automatically a child of the myinterface interface, no special keywords needed. This means we can use myinterface in place of mystruct in our code, like this:

func main() { ms := mystruct{} testfunction(ms) } func testfunction(mi myinterface) { } 1 2 3 4 5 6 7 func main ( ) { ms : = mystruct { } testfunction ( ms ) } func testfunction ( mi myinterface ) { }

In the above code testfunction takes an argument of type myinterface. However, since mystruct is a child of myinterface, the testfunction function will happily accept a value of type mystruct as an argument.

The caveat here though, is that in order to get back a value of type of mystruct from a value of type myinterface, we need to use a Go feature called ‘Type assertion’. Here is how this would look like:

func testfunction(mi myinterface) { ms := mi.(mystruct) //type assertion ms.method1() } 1 2 3 4 func testfunction ( mi myinterface ) { ms : = mi . ( mystruct ) //type assertion ms . method1 ( ) }

In the above code, we used type assertion to extract mystruct from it’s parent interface myinterface.

Type assertion is a useful feature in Go that involves extracting a concrete type from it’s parent interface. Because of this, you can only use Type assertions on interfaces.

This implicit relationship between myinterface and mystruct is what makes the empty interface special. Since the empty interface doesn’t include any methods, then in practice, any Go type is implicitly a child of the empty interface. In other words, we can substitute any Go type with the empty interface. Consider the testfunction example again, it could have been written as follows with the same result as before:

func testfunction(mi interface{}) { ms := mi.(mystruct) ms.method1() } 1 2 3 4 func testfunction ( mi interface { } ) { ms : = mi . ( mystruct ) ms . method1 ( ) }

However, since the empty interface type could accept any type and not just mystruct, we can substitute it with any other Go type as we please:

func testfunctionint(i interface{}){ myint := i.(int) fmt.Println("myint is: ", myint) } func testfunctionstring(i interface{}){ mystring := i.(string) fmt.Println("mystring is:", mystring) } func main() { myint := 4 testfunctionint(myint) mystring := "hello" testfunctionstring(mystring) } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func testfunctionint ( i interface { } ) { myint : = i . ( int ) fmt . Println ( "myint is: " , myint ) } func testfunctionstring ( i interface { } ) { mystring : = i . ( string ) fmt . Println ( "mystring is:" , mystring ) } func main ( ) { myint : = 4 testfunctionint ( myint ) mystring : = "hello" testfunctionstring ( mystring ) }

In the above code, we made use of the empty interface to act as an int type in one case, and a string type in another. This all looks nice, but I am sure you would agree with me that the code could use more elegance! Luckily Go comes to the rescue with another feature called ‘type switch’, here is how this would look like:

func testfunction(i interface{}) { //type switch switch v := i.(type) { case string: fmt.Println("mystring is:", v) case int: fmt.Println("myint is: ", v) } } func main() { myint := 4 testfunction(myint) mystring := "hello" testfunction(mystring) } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func testfunction ( i interface { } ) { //type switch switch v : = i . ( type ) { case string : fmt . Println ( "mystring is:" , v ) case int : fmt . Println ( "myint is: " , v ) } } func main ( ) { myint : = 4 testfunction ( myint ) mystring : = "hello" testfunction ( mystring ) }

As you can infer from the above code, the ‘type switch’ feature is a simple switch statement that is capable of switching between datatypes. In our case, we had two options, either a string type or an int type. The code above will produce the same results as the other code snippet that made use of the testfunctionstring, and testfunctionint functions.

Perfect, with this, you should have a good idea of what the empty interface in golang is about, and why it usually gets mentioned in situations where generics are expected to help. Most of Go APIs make use of the empty interface to accept multiple types.

Consider the fmt package for example, a very popular function in the package is fmt.Println which is used to write a new line to the standard output. Most of us use this function whenever we want to print out a quick line to debug a program or see an output. If you dive deeper into this function, you’d find that the function signature looks like this:

func Println(a ...interface{}) (n int, err error) 1 func Println ( a . . . interface { } ) ( n int , err error )

The function takes a dynamic list of arguments, all of them are of the empty interface type. This is why when I type fmt.println(“hello”,4) , I get no complains about unmatched types.

Now comes the important question: why does everybody complain about the fact that Go does not have decent support for generics, if we can make use of the power of the empty interface, then go on to live happily ever after?

The answer is simple: In almost all cases, when we make use of the empty interface in Go, we have to convert the value back to the original type at runtime in order to obtain the original value (remember how we had to use type assertion or type switch?). This is similar in principle to the concept of ‘boxing’ in other languages, where we can use a parent type (type ‘Object’ for example in Java) to represent all child types, then figure out at runtime how to get the child from parent. This typically comes with some price in performance.

Generics, on the other hand, don’t work that way. For generics, the type checking happens at compile time, and has no visible overheads at runtime.

There is an interesting stackoverflow thread discussing the performance of type assertions, type castings, and type switches in Go , they have been steadily improving over the years. However, the fact still stands that you have to design your code in a way to expect a type conversion somewhere after the usage of the empty interface: https://stackoverflow.com/questions/28024884/does-a-type-assertion-type-switch-have-bad-performance-is-slow-in-go

Some people think that Go doesn’t need full generics support, while others very strongly disagree. The purpose of this article is not to prove one point of view is right or wrong, but rather to shed some light into how you can utilize the empty interface in your code to build more flexible software.

Enjoyed this article? check out my new online course: Modern Golang Programming, where we cover how to build powerful and modern applications in the Go language.

Care to share? ... Linkedin Reddit

Share this: Share

Facebook

LinkedIn



Reddit

Twitter



Tumblr

