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! 💥 🎉 🎊
🎉 Go 1.20 is released!
— Go (@golang) February 1, 2023
📝 Release notes: https://t.co/V6huayE5Vi
⬇️ Download: https://t.co/DBbJPEVUJS#golang pic.twitter.com/ubWr2MBycj
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:
- MacOS Homebew,
- Official Go 1.20 Docker Images
- Alpine 3.17:
docker pull golang:1.20.0-alpine3.17
- Debian Bullseye
docker pull golang:1.20.0-bullseye
- Alpine 3.17:
- Github Actions.
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:
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:
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 joinsErrOne
andErrTwo
and returns only one. - L17-L23: demonstrate that using
errors.Is
supports multiple errors, therefore it allows to detect eitherErrOne
orErrTwo
- L26: uses the verb
%w
to wrap multiple errors, similar behavior toerrors.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:
But if we use GODEBUG=tarinsecurepath=0 ./tar
then it will detect the malicious file:
Similarly, for archive/zip
if we don’t use the environment variable it will render all content:
But if we use GODEBUG=zipinsecurepath=0 ./zip
then it will detect the 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!
Recommended reading
If you’re looking to sink your teeth into more Go-related topics I recommend the following books: