rss resume / curriculum vitae linkedin linkedin gitlab github twitter mastodon instagram
Learning Go: Interface Types - Part 1
Jun 24, 2021

Disclaimer: This post includes Amazon affiliate links. If you click on one of them and you make a purchase I’ll earn a commission. Please notice your final price is not affected at all by using those links.

Interface Types is one of the most powerful features in the Go programming language, when used correctly it could lead to code easier to maintain and to extend. Depending on your programming experience this concept could be a bit hard to understand because maybe it’s the first time you hear it, or the programming language you’re familiar with does not have an equivalent concept or if it does the way it’s implemented differs to the way Go implements it.

Interfaces Types are types, therefore we need to understand clearly what a is a type and what are the types supported in Go.



What is Type?

In Go a Type is a way to represent state or behavior. There are two kinds of types: Basic Types and Composite Types.

Basic Types include your well-known types that are used to store data in a particular way, specifically:

  • string, for example: "hello",
  • numeric, which includes types of numbers represented either as integer, float or complex numbers. There are numeric types meant to describe the maximum allowed capacity and the possible values supported, for example uint8 represents an unsigned integer that has a capacity of 8 bits.
  • bool, that defines a boolean value that allows either true or false

Those would be considered the “basic” Basic Types meaning other types use them via an alias to define new types, this is to differentiate their goal but in the end they reuse the capacity of the original Basic Type. For example the byte type is an alias to uint8.

Composite Types include types that are a bit more complicated and behind the scenes are composed by other multiple types, specifically:

  • pointer, used to hold the memory address of a value,
  • function, defines a collection of statements that work together,
  • map, data structure used to map keys to values,
  • array, defines a static collection of values,
  • slice defines a dynamically-sized array,
  • struct defines a way to create a new custom type that has a collection of fields,
  • channel, defines a conduit used for sending and receiving values, and finally
  • interface, a set of method signatures.

Functions and Methods

Defining variables using those types allows us to use them in conjunction with Functions, which are used to modularize our code and allow some reusability. Because Go is not an Object Oriented Programming Language and there are no classes Functions come handy when trying to attach behavior to our custom types, when we do that a new concept is introduced: Methods.

Methods are Functions that receive a special first argument behind the scenes. This argument type is the same as the type defining the method and its value is the one that invokes the function, it’s used to somehow mimic what Object Oriented Programming Languages do with classes.

For example, a simple type to indicate three different levels of priority could be defined as:

type Priority int8

func (p Priority) String() string {
	switch int8(p) {
	case 0:
		return "low"
	case 1:
		return "high"
	}

	return "unknown"
}

The code above creates a new type called Priority that uses the int8 basic type, behavior is added after implementing the String method, which returns a string representing the value stored in the custom type.

To make use of this behavior we have a few different ways, let’s look at the following code:

func main() {
	var p Priority = 1

	fmt.Println(p.String())         // "high"
	fmt.Println(Priority.String(p)) // "high"
	fmt.Println(p)                  // "high"
}

The output will be the same for all three cases but the important thing in this example is how the method String is being invoked:

  • p.String(): because this is a method call, the variable p is passed in behind the scenes as the first argument to the String function of the type Priority and executed as usual.
  • Priority.String(p): recall that Methods are Functions therefore we can still make a call to the function using this style, it’s definitely uncommon to see this style but seeing this call in action clarifies even more the previous statement: Methods are Functions.

Finally the last fmt.Println line does something interesting because it doesn’t call String explicitly. How does this work? This is where Interface Types come in.

What are Interface Types?

Interface Types are a set of method signatures that can hold any value that implements those methods. Types implementing that set of methods are automatically compatible with the Interface Type being defined, in other words compared to other programming languages Interface Types in Go are implicit.

This means types meant to implement an interface do not have to know the Interface Type in advance to implement it or to extend it, like other languages require.

Interface Types bring one of the features of Object Oriented Programming languages: Polymorphism.

If a type satisfies a set of method signatures then that type is equivalent to an Interface Type that defines said methods. This is why the third call in fmt.Println does not require the explicit call to String, the implementation of fmt.Println defined an Interface Type as mentioned in the documentation:

If an operand implements method String() string, that method will be invoked to convert the object to a string, which will then be formatted as required by the verb (if any).

Specifically in this case, in the fmt package there’s an Interface Type type called Stringer that defines the String method that our Priority type implements which is in the end used for printing out the string value.

Conclusion

Like I mentioned when I started this post, Interface Types are a great feature in Go, it allows us to build Polymorphic code that could handle different implementations, but not only that it also allows extending existing code because there’s no need to explicitly define/import/extend Interface Types.

In the next post I will cover other important concepts related to Interface Types like the empty interface interface{}, nil types and their internal representation.


Back to posts