Back

Go 1.18 Comes With Many Amazing Changes

Go 1.18 is due to be released in February and it contains multiple changes that will improve the language

by Percy Bolmér, January 31, 2022

Image by Percy Bolmér. Gopher by Takuya Ueda, Original Go Gopher by Renée French (CC BY 3.0)
Image by Percy Bolmér. Gopher by Takuya Ueda, Original Go Gopher by Renée French (CC BY 3.0)

The long-awaited Go 1.18 is just around the corner, and it is probably the most discussed update to Go in a long time. There are some major new things added to the language, with Generics standing out the most.

Many of the new stuff being added is missed because everybody is focused on generics, so let’s make a quick overview of the all changes that are coming. There are changes that I feel are important to know about in the coming update, that you don’t see mentioned that much.

For some of the Major features, I have an in-depth article that you are welcome to read if you want a deeper look at the feature.

If you would rather watch a video, you can find this article on my Youtube

Generics

A visual explanation of Generics, a line represented by a function, and the generic one accepts multiple inputs. Image by Percy Bolmér. Gopher by Takuya Ueda, Original Go Gopher by Renée French (CC BY 3.0)
A visual explanation of Generics, a line represented by a function, and the generic one accepts multiple inputs. Image by Percy Bolmér. Gopher by Takuya Ueda, Original Go Gopher by Renée French (CC BY 3.0)

As already mentioned earlier, Go is finally getting generics. This seems to be a topic that can upset developers, some developers seem to love the idea, some seem to hate it.

Generics is a way of allowing a function to accept multiple different data types. This is useful when you have to perform the same thing for many different types.

The lack of generics in Go has previously been solved by, duplicate functions per data type, or the use of an empty interface{} that is typecast with a massive switch to handle the needed data types. These two workarounds are not uncommon solutions to the lack of generics. These solutions, or, kind of hacks can be removed with the release of 1.18.

Gone are the days of the []interface{} hack — Generic Developer

Imagine you have to summarize the values for a slice of Integers and a slice of Floats. This would require you to have two duplicate functions, the functions would be the same, an for range that goes through and adds the values and return the result. The only difference would be the defined input parameters data type.

Before generics, this required a summary function specific to each data type like the gist below.

package main

import (
	"fmt"
)

func main() {
	intvalues := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	int64values := []int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	float64values := []float64{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0}

	fmt.Println("Total for ints:", CountTotalInts(intvalues))
	fmt.Println("Total for int64:", CountTotalInts64(int64values))
	fmt.Println("Total for floats:", CountTotalFloats(float64values))

}
// CountTotalInts64 counts the total for the slice
func CountTotalInts64(points []int64) int64 {
	var total int64
	for _, v := range points {
		total += v
	}
	return total
}

// CountTotalInts counts the total for the slice
func CountTotalInts(points []int) int {
	var total int
	for _, v := range points {
		total += v
	}
	return total
}

// CountTotalFloats counts the total for the slice
func CountTotalFloats(points []float64) float64 {
	var total float64
	for _, v := range points {
		total += v
	}
	return total
}
CountTotal for different data types solved with dupelicate functions

With generics, all those three summary functions can be done with the same function instead.

Let’s take a look at how this looks when solved with generics instead.

package main

import (
	"fmt"
)

func main() {
	intvalues := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	int64values := []int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	float64values := []float64{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0}

	fmt.Println("Total for generic ints:", CountTotal[int](intvalues))
	fmt.Println("Total for generic int64:", CountTotal[int64](int64values))
	fmt.Println("Total for generic floats:", CountTotal[float64](float64values))
}

// CountTotal is used to count the total for a slice of data type int64, int or float64
func CountTotal[V int64 | int | float64](points []V) V {
	var total V

	for _, value := range points {
		total += value
	}
	return total
}
A generic solution for summarizing the total values

That is a massive improvement according to me. Not only is the amount of code reduced, but it is also easier to maintain, read, and a less complex solution.

If you want to read more about how to use generics, you can read my article about it. In it, we cover how to use generics and what more you can do with it.

Generics is released in Go 1.18 and it is time to learn how to leverage this new feature
Learning Generics in Go

January 31, 2022

Generics is released in Go 1.18 and it is time to learn how to leverage this new feature

Read more

Fuzzing tests

Fuzzing in Go, allows us to test randomly generated input to a function. Image by Percy Bolmér. Gopher by Takuya Ueda, Original Go Gopher by Renée French (CC BY 3.0)
Fuzzing in Go, allows us to test randomly generated input to a function. Image by Percy Bolmér. Gopher by Takuya Ueda, Original Go Gopher by Renée French (CC BY 3.0)

Fuzzing is one of the things I like most about the update. I am surprised people are not talking more about it. It will give us the ability to generate input parameters during testing.

It might not sound like much, but it allows us to easily expand the unit tests to locate and track down more bugs. Table Driven Tests allows us to give many different inputs, but still, you won’t write a few hundred different test inputs probably, but fuzzing can allow this to be generated.

Fuzzing allows you to test thousands of different inputs, this helps finding hidden bugs, bugs you would probably not have thought of yourself to check for.

The idea about fuzzing is that by generating a bunch of different inputs to your functions, there is a higher chance of finding breaking edge cases. The fuzzing package will take in an example input to use as a base, called a seed corpus. The fuzzer will use those examples as a starting point and modify that to find values that cause your function to break.

Fuzzing will be added to the testing package, and it will be a new struct used in the same way as testing.T but instead testing.F.

An example on fuzzing from go.dev looks like this, note how they add the GET query parameters as a seed corpus, this will allow the fuzzer to generate thousands of similar inputs.

//go:build go1.18
// +build go1.18

package fuzz

import (
    "net/url"
    "reflect"
    "testing"
)

func FuzzParseQuery(f *testing.F) {
    f.Add("x=1&y=2")
    f.Fuzz(func(t *testing.T, queryStr string) {
        query, err := url.ParseQuery(queryStr)
        if err != nil {
            t.Skip()
        }
        queryStr2 := query.Encode()
        query2, err := url.ParseQuery(queryStr2)
        if err != nil {
            t.Fatalf("ParseQuery failed to decode a valid encoded query %s: %v", queryStr2, err)
        }
        if !reflect.DeepEqual(query, query2) {
            t.Errorf("ParseQuery gave different query after being encoded\nbefore: %v\nafter: %v", query, query2)
        }
    })
}
go.dev — Example fuzzing

If you want to learn about how to use the fuzzer and write your fuzzing functions, for, etc HTTP Handlers or more, then you can read my article about it.

Fuzzing is a technique where you automagically generate input values for your functions to find bugs
Fuzzing Tests in Go

January 31, 2022

Fuzzing is a technique where you automagically generate input values for your functions to find bugs

Read more

Workspaces

Workspace mode allows us to replace versions and modules in a better way. Image by Percy Bolmér. Gopher by Takuya Ueda, Original Go Gopher by Renée French (CC BY 3.0)
Workspace mode allows us to replace versions and modules in a better way. Image by Percy Bolmér. Gopher by Takuya Ueda, Original Go Gopher by Renée French (CC BY 3.0)

Workspaces are released to allow developers to easily switch the version of modules to be used inside a project. The reason for this is to ease the development while working on multiple modules at the same time.

If you have been using go modules in a company environment you most likely have been forced to add a replace directive to change or force a local version of a package to be used.

The replace directive is easy to forget to remove from your go.mod file, which will break the module for other developers if pushed to the repository. Since the tooling will now use the path given in the go.mod file, which most likely won’t exist on another developer’s computer.

If you are unfamiliar with replace, you can replace the module to use inside the go.mod file by telling it to replace a certain module with either a local path or particular version, to make it grab a local fork instead of fetching the library with go get

module github.com/programmingpercy/mymodule

replace github.com/programmingpercy/mymodule => /home/users/percy/experimental/mymodule

require (
	github.com/programmingpercy/mymodule v1.0.0
)
go.mod — Minimal replace directive.

You can see in the gist how I tell the go tooling that any time it sees github.com/programmingpercy/mymodule it should fetch that from a local path on my computer at /home/users/percy/experimental/mymodule instead of using go get

Finally, we have a remedy against replace directives in go.mod files. This is pretty big for me, I often find myself in situations working on multiple modules at the same time and making changes in them simultaneously.

To start using workspace mode as they call it, you initialize the workspace by using the regular go tooling. Similar to creating a module, you run go work init. This will create your workspace file that we can start using.

Note that workspace mode will make many of the go toolings start using the go.work file and modify it for you. The go team states that you should not push the go.work file to repositories, as this will reintroduce the problem of the replace inside go.mod files.

The workspace mode will force all modules mentioned in the go.work to be used, similar to the go.mod file. We can still use the replace directive to specify the module to use instead. This allows you to keep using the replace directive while keeping your go.mod file clean, and only .gitignore the go.work.

In the proposal, more features are mentioned, the ability to switch between different workspaces to test multiple dependency versions easily, etc. Can’t wait to try this feature out more.

Honorable Mentions

There are some changes worth a mention, but not big enough to have their chapter.

any — If you start seeing any appear in go codebases don’t panic. This is a new predeclared alias for interface{}

comparable — An predeclared alias for an interface that contains all the types that can be compared using == and !=. This is connected to generics and to be used as a constraint.

net/netip — Seems that we will get a new package for handling IP addresses. I have done a lot of networking in Go, and it has been painful to handle IP addresses. It looks like I was not the only one to experience this, lets hope this solution is better.

strings.Cut — The improvement I never knew we needed. The strings package comes with a new function, Cut which will find a separator and return anything found before and after the separator. It will also return a bool indicating if the separator was found.

IP:PORT — I’m looking at you

Conclusion

This update will bring many welcome changes. The go team has been working hard, we can tell by the many great changes. I am particularly fond of the introduction to workspaces and fuzzing.

The go language is maturing and as a Gopher, I am proud to watch it grow up.

If you like my writing, make sure you check out the in-depth articles on the major topics if you want to learn how to use the new features.

You should read about Generics and Fuzzing.

What are you waiting for, download the 1.18 update already and start using all the new shiny swag!

Hope you enjoyed, and feel free to reach out using any of my social medias listed below.

If you enjoyed my writing, please support future articles by buying me an Coffee

Sign up for my Awesome newsletter