rss linkedin linkedin gitlab github twitter mastodon instagram
What is new in Go 1.20?
Feb 03, 2023

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

It’s that time again! Another minor version of Go was released, superseeding Go 1.19. Released a few days ago on Febraury 1, 2023, this is Go 1.20! ūüí• ūüéČ ūüéä

What is new?

Similar to the 1.19 release there are no changes to the language but rather additions to the runtime, libraries and toolchain. In this post I will cover 8 new features I believe are worth calling out, however consider reading the full release notes because I bet you will find something that is relevant to you.


Updating

Depending on the platform you’re using for development and the production system you use for running your services you may already be able to use Go 1.20, if not, the official download page provides prepackaged versions for different platforms.

For more concrete examples:


1. Subcommands accept -C to change directory

Code for this example is available on Github.

Excerpt from official release notes:

The go subcommands now accept -C <dir> to change directory to <dir> before performing the command, which may be useful for scripts that need to execute commands in multiple different modules.

This is easily demonstrated by going to the root path of the example, there you will see two simple applications example1 and example2.

Previously in order to execute a Go subcommand you had to change to the directory where the go.mod was located, with Go 1.20 we can skip that step and instead use the parameter -C <folder> to do that automatically, for example running:

go build -C example1 github.com/MarioCarrion/videos/2023/02/03/01-accept-c/example1

Will build a binary example1/example1, and running:

go build -C example2 github.com/MarioCarrion/videos/2023/02/03/01-accept-c/example2

Will build a binary example2/example2, a much more real-life example would be the one I have in make tools:

 1tools:
 2	go install -C internal/tools \
 3		-tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate
 4
 5	go install -C internal/tools \
 6		github.com/deepmap/oapi-codegen/cmd/oapi-codegen \
 7		github.com/fdaines/spm-go \
 8		github.com/golangci/golangci-lint/cmd/golangci-lint \
 9		github.com/kyleconroy/sqlc/cmd/sqlc \
10		github.com/maxbrunsfeld/counterfeiter/v6 \
11		goa.design/model/cmd/mdl \
12		goa.design/model/cmd/stz \

That installs the needed binaries in $GOBIN.

2. Skip pattern support for go generate and go test

Code for this example is available on Github.

Excerpt from official release notes:

The¬†go¬†generate¬†command now accepts¬†-skip¬†“pattern”¬†to skip¬†//go:generate¬†directives matching¬†“pattern”.

The¬†go¬†test¬†command now accepts¬†-skip¬†“pattern”¬†to skip tests, subtests, or examples matching¬†“pattern”.

This new feature applies to both go generate and go test, it literally means using the new argument -skip <pattern> to skip certain patterns when running either subcommand.

For example for go generate, using the following code:

 1package skip
 2
 3//go:generate stringer -type=Currency
 4
 5type Currency int
 6
 7const (
 8	USD Currency = iota
 9	MXN
10	PHP
11)
12
13//go:generate stringer -type=Country
14
15type Country int
16
17const (
18	USA Country = iota
19	Mexico
20	Phillipines
21)

Will call stringer to generate the string types corresponding to the constants we defined, however if we make a change to Country and add Fake:

17const (
18	USA Country = iota
19	Mexico
20	Phillipines
21	Fake
22)

And then run:

go generate -skip="Country" github.com/MarioCarrion/videos/2023/02/03/02-skip

Will ignore the value we just added. Similarly for tests:

 9package skip_test
10
11import (
12	"testing"
13
14	skip "github.com/MarioCarrion/videos/2023/02/03/02-skip"
15)
16
17func Test_String(t *testing.T) {
18	// ...
19}
20
21func Test_Integer(t *testing.T) {
22	// ...
23}

If we want to skip Test_String we could run:

go test -skip="String" -v ./...

3. Code coverage for running applications

Code for this example is available on Github.

Excerpt from official release notes:

The go build, go install, and other build-related commands now support a -cover flag that builds the specified target with code coverage instrumentation.

What this means in practice is that we can generate code coverage profiles not only from unit tests but also from running applications, and more importantly existing tools can allow us to visualize the results, for example in HTML.

Take the following example:

 9func main() {
10	fmt.Println("hello world!!!")
11
12	if true == false { // ūü§£
13		fmt.Println("I'm not covered!")
14	}
15}

It’s a silly one because the line 13 will never execute, because true == false is never going to be true! With this new code coverage support for running applications we can run the application using the new GOCOVERDIR variable and pass in a directory that will be used to save the code coverage profile, for example in this case we use cover/:

GOCOVERDIR=cover ./main

Then using the existing code coverage tools we can convert that into an HTML page we can browse:

go tool covdata textfmt -i=cover -o profile.txt
go tool cover -html=profile.txt

Which looks like this:

Go 1.20 Code Coverage

4. go vet reports incorrect time format

Code for this example is available on Github.

Excerpt from official release notes:

The vet tool now reports use of the time format 2006-02-01 (yyyy-dd-mm) with Time.Format and time.Parse. This format does not appear in common date standards, but is frequently used by mistake when attempting to use the ISO 8601 date format (yyyy-mm-dd).

If you’re using a modern editor such Neovim or Visual Code that supports gopls, then the warning will render right away:

Go 1.20 go vet time warning

But also running:

go vet main.go

Displays a similar output:

# command-line-arguments
./main.go:10:26: 2006-02-01 should be 2006-01-02

5. errors support wrapping multi-errors

Code for this example is available on Github.

Excerpt from official release notes:

Go 1.20 expands support for error wrapping to permit an error to wrap multiple other errors.

An error e can wrap more than one error by providing an Unwrap method that returns a []error.

The errors.Is and errors.As functions have been updated to inspect multiply wrapped errors.

The fmt.Errorf function now supports multiple occurrences of the %w format verb, which will cause it to return an error that wraps all of those error operands.

The new function errors.Join returns an error wrapping a list of errors.

This expands the changes that were introduced in Go 1.13 allowing wrapping multiple errors using both the %w verb and a new function called Join, for example:

 9var ErrOne = fmt.Errorf("one")
10var ErrTwo = fmt.Errorf("two")
11
12func main() {
13	// Using errors.Join
14	join := errors.Join(ErrOne, ErrTwo)
15	fmt.Printf("%+v\n", join)
16
17	if errors.Is(join, ErrOne) {
18		fmt.Println("One!")
19	}
20
21	if errors.Is(join, ErrTwo) {
22		fmt.Println("Two!")
23	}
24
25	// Using fmt.Errorf with multiple %w
26	efmt := fmt.Errorf("%w, %w", errors.New("fmt"), io.EOF)
27
28	fmt.Printf("%+v\n", efmt)
29
30	if errors.Is(efmt, io.EOF) {
31		fmt.Println("End of File")
32	}
33}
  • L14: uses the new errors.Join function to wrap multiple errors, it joins ErrOne and ErrTwo and returns only one.
  • L17-L23: demonstrate that using errors.Is supports multiple errors, therefore it allows to detect either ErrOne or ErrTwo
  • L26: uses the verb %w to wrap multiple errors, similar behavior to errors.Join, it returns only one, and
  • L30-32: like we did before errors.Is works as well.

6. archive/tar and archive/zip security improvements

Code for this example is available on Github.

Excerpt from official release notes:

archive/tar: When the GODEBUG=tarinsecurepath=0 environment variable is set, Reader.Next method will now return the error ErrInsecurePath for an entry with a file name that is an absolute path, refers to a location outside the current directory, contains invalid characters, or (on Windows) is a reserved name such as NUL. A future version of Go may disable insecure paths by default.

archive/zip: When the GODEBUG=zipinsecurepath=0 environment variable is set, NewReader will now return the error ErrInsecurePath when opening an archive which contains any file name that is an absolute path, refers to a location outside the current directory, contains invalid characters, or (on Windows) is a reserved names such as NUL. A future version of Go may disable insecure paths by default.

This new improvement brings two new environment variables that are used to enforce this new security improvement, this is to prevent bad actors to include in either format malicious files that could override our local ones when untaring/unzipping respectively.

For example using the archive/tar example, if we create a malicious tar file: tar -cvf example.tar example ../../../../LICENSE and run the binary without the flag it will render the contents as is:

Go 1.20 archive/tar rendering malicious file

But if we use GODEBUG=tarinsecurepath=0 ./tar then it will detect the malicious file:

Go 1.20 archive/tar NOT rendering malicious file

Similarly, for archive/zip if we don’t use the environment variable it will render all content:

Go 1.20 archive/zip rendering malicious file

But if we use GODEBUG=zipinsecurepath=0 ./zip then it will detect the malicious file:

Go 1.20 archive/zip NOT rendering malicious file

The Go team mentioned this may be enabled by default in future releases.

7. context’s new method WithCancelCause

Code for this example is available on Github.

Excerpt from official release notes:

The new WithCancelCause function provides a way to cancel a context with a given error. That error can be retrieved by calling the new Cause function.

This is an improvement of the existing WithCancel function that allows us to cancel a context by calling the returned function with an explicit error. Take the following example:

 9func main() {
10	ctx, cancel := context.WithCancel(context.Background())
11	cancel()
12
13	fmt.Printf("error is: %v\n", ctx.Err())
14	fmt.Printf("cause was: %v\n", context.Cause(ctx))
15
16	//-
17
18	ctxCause, cancelCause := context.WithCancelCause(context.Background())
19	cancelCause(errors.New("something failed"))
20
21	fmt.Printf("error is: %v\n", ctxCause.Err())
22	fmt.Printf("cause was: %v\n", context.Cause(ctxCause))
23}

If we run it the output will be:

error is: context canceled
cause was: context canceled
error is: context canceled
cause was: something failed

The first two lines correspond to the WithCancel call and the next two correspond to the WithCancelCause call, the important bit to call out is is the use of context.Cause. That Cause function will determine whether it was coming from WithCancel, and therefore it was context.Canceled; or if it came from WithCancelCause, and therefore the cause was the custom error.

8. time’s new layout and Compare method

Code for this example is available on Github.

Excerpt from official release notes:

The new time layout constants DateTime, DateOnly, and TimeOnly provide names for three of the most common layout strings used in a survey of public Go source code.

The new Time.Compare method compares two times.

Take the following example:

20	fmt.Printf("now(future) %v (-1 = future)\n", now.Compare(future))
21	fmt.Printf("now(past) %v (+1 = past)\n", now.Compare(past))
22	fmt.Printf("now(now) %v (0 = same)\n", now.Compare(now))
23
24	//-
25
26	fmt.Println(now.Format(time.DateTime))
27	fmt.Println(now.Format(time.DateOnly))
28	fmt.Println(now.Format(time.TimeOnly))
  • L20-L22: Compare will be used to determine whether the now and the received value is before, after or if they are the same.
  • L26-L28: Use the new constants that simplify most common layout strings.

Conclusion

We have to thank the work done by the Go team, this was an interesting release that included some new features, I’m surprised with the code coverage support for running applications, it was something I wasn’t expecting and also having multi errors is a nice addition; there are also some other improvements related to performance as well, like I said in the beginning I highly encourage you to read the complete release notes, I bet there’s something that you can find useful.

Great job Go team, looking forward to Go 1.21!

If you’re looking to sink your teeth into more Go-related topics I recommend the following books:


Back to posts