Go 1.18: the version of Go you always wanted

The first version of the Go programming language was released in March 2012 by Google. Ten years later and the latest version, Go 1.18, was released in Feb 2022.

21-02-2022
Bcorp Logo


Go 1.18 the version of Go you always wanted but didn't know to ask for!

The first version of the Go programming language was released in March 2012 by Google. Ten years later and the latest version, Go 1.18, was released in Feb 2022. It is a major update to the language thanks to the inclusion of generics. This brings Go in line with other modern programming languages such as Java, C# and Scala in terms of generic typed data structures and functions. Go’s version of generics will be the primary topic of this blog.

Of course, the introduction of Generics / Type Parameters is not the only thing in the 1.18 release, but it is the most significant. Other features in this release include Fuzzy Tests, the introduction of the any type and the comparable interface and the new package net/netip. These will be discussed briefly at the end of this blog.

Why no Generics in the original Go?

Go was originally designed to try to be as fast as possible, within the constraints of a modern programming language. As such the general aim was to be as fast as C within a language that provided many of the features of a modern programming language such as Java, Scala or C#.

As part of this goal, some design decisions were made to help optimise the performance of any runtime programme. One such decision was to omit Generic types and functions from the original language.

This decision was made because Generics add significantly to the complexity of the type system. This has a significant impact on the compiler and runtime which can introduce overheads that could impact the performance of the Go runtime. Back in the noughties when Go was being designed, the developers of the language wrote about Generics:

“we haven’t yet found a design that gives value proportionate to the complexity”.

They also stated that they would continue to consider the issue. Wind forward to 2022 and they believe that their compiler and runtime technology is now up to the task and have introduced Generics into the Go language.

Why introduce Generics into Go?

If Go has been able to thrive in production environments for over a decade with Generics, why introduce them now?

The term Generics or Generic Types / Functions is generally used in programming languages to indicate a type or function in which a placeholder (the generic) is used instead of an actual type. When the generic type or function is then used, the generic placeholder is replaced with a concrete type.

Thus, a Generic Queue type can be created, this generic type can define all the behaviour of a Queue but leave the actual content type to be specified when a Queue is instantiated. That is, when the Queue is created it is possible to specify that a Queue of Integers, or Strings or Booleans is being used.

From a Software Engineering point of view, the benefit of Generics in a language is that the generic placeholders can restrict the values held or processed in a type safe way. However, the definition of the actual type or function is general enough to have wide applicability.

Generics in Go

In Go the concept of Generics is known as Type Parameters. These allow a function or a struct to be defined with a type parameter (a placeholder) that can eb ‘filled in’ at a later date.

For example, a pre Type Parameters max() function in Go might be defined as:

func max(a, b int)int {
  if
a < b {
    return
b
  }
  return
a
}

However, the basic logic in this function can be used to compare any values of any numeric type. The only constraint is that they must understand the ‘<’ (less than) operator.

In Go 1.18 we can therefore use Type Parameters to specify a version of the max function which has a Type Parameter that can be used allow any numeric type to be used with the function. For example:

type Numeric interface {
  int | int64 | uint | float32 | float64
}
func max[T Numeric](a, b T)T {
  if
a < b {
    return
b
  }
  return
a
}

In the above T is a Type Parameter for the function max. It is defined within ‘{} brackets as part of the function header.

The Type Parameter is used within the rest of the function definition as a normal type. That is the type of a and b and the return type T will be specified when the function max is used. Now the interface Numeric defines a constraint on T. This constraint says that the actual type used must be one of int, int64, uint, float32 or float 64. This is indicated by the ‘|’ within the interface which represents an OR relationship between the types being listed.

When the function is called, the type for T must either be specified explicitly or inferred by the compiler from the parameter types passed to the function, for example:

func main() {
  println(max[int](1, 2))
  println(max(2, 3))
  println(max(2.1, 3.1))
}

In the first example, the type to be used for T is explicitly specified to be int, whereas in the second and third the compiler infers the type based on the parameters (in this case the types are int and float64 respectively).

Generics can also be used with Structs.

Structs are the closest thing Go has to classes in other languages (although they are not classes). They are structures that can hold zero or more values and have associated functions (often referred to as methods) associated with them.

For example, a Node Struct for a simple Linked List implementation can be defined as:

type Node[T any] struct {
  Value T
  Next *Node[T]
}

This indicates that a Node can be instantiated to any type of thing. However, when it is instantiated, a specific type will be used for T and that the Node (and the next Node in the list) will all hold the same concrete type.

To use the Node type we can explicitly specify the type to be held as well as initialise any of the Structs’ fields, for example:

type Node[T any] struct {
  Value T
  Next *Node[T]
}

This can then be used as shown below:

func main() {
  intNode := Node[int]{}
  println(intNode)
  stringNode := Node[string]{Value: "Golang" }
  println(stringNode)
}

Type parameters can also be used on methods associated with the Struct, for example: 

func (n Node[T]) prettyPrintValue() {
  fmt.Printf("value: %v\n", n.Value)
}

Other new features of Go 1.18

There are several other noteworthy features of this release, including:

any Type any is a predefined alias for the empty interface interface{}. In Go 1.18 any type that implements all the functions in an interface is said to satisfy that interface. As the empty interface does not contain anything all types satisfy the empty interface. This was previously how a reusable function or struct was created. However, seeing interface{} as a parameter or a field value could be confusing, particularly for those new to Go, use the type any will make code clearer and more intuitive.

comparable This is an interface that can be used for things that can be compared using == and !=.

net/netip This is a new package that directly supports working with IP addresses.

strings package changes The strings.Cut function allows a string to be cut into two via a separator. This function will return two strings, the first will be a string up to the separator and the second will be a string following the separator. Thus, it could be used to separate the string https://www.frameworktraining.co.uk/ into the protocol and the domain. The new strings. Clone function copies the input string.

Go 1.18 the version of Go you always wanted but didn't know to ask for!


Fuzzy Testing. Fuzzy Tests or Fuzzing is a new feature allows a test to be run against many randomly generated inputs. For example, the max function defined earlier could be run against a set of predefined values, including (-1, -1), (0, 0), (1, 1) etc. However, these need to be manually defined and important test cases could be missed. Using Fuzzy Testing it will be possible to get the Go test framework to generate a large number of random values to be provided and used to test the max() function.

Summary

The inclusion of Generics / Type Parameters in Go will make the development of generic, strongly typed, data structures and functions much easier. This should help with the development of libraries as well as widen interest in the Go programming language itself.


Would you like to know more?

If you found this article interesting you might be interested in our Go Programming Training Course.

Share this post on:

We would love to hear from you

Get in touch

or call us on 020 3137 3920

Get in touch