Get an overview of Go 1.20's new features and noteworthy changes. Plus: A feature that did not make it into the release.
Go 1.20 contains a wagonload of changes. I will focus on the most interesting features, noteworthy changes, and a dangerous change that did not make it into the Go 1.20 docs.
The most eagerly awaited feature (according to one commenter on Reddit) is the new WithCancelCause()
function of the context
package.
It works like WithCancel
except that the returned cancel
function takes an error value as a parameter. The goroutine that calls cancel()
can pass an error to it that describes the cause of the cancelation.
Interested parties can call context.Cause()
after the cancelation to retrieve that custom error.
// WithCancelClause() can be used in place of WithCancel()
ctx, cancel := context.WithCancelCause(context.Background())
// cancel() expects an error value
cancel(errors.New("steam engine transmission belt broken"))
// ctx.Err() says "context canceled" as before
fmt.Println(ctx.Err())
// but now you can retrieve the cause of the cancelation
fmt.Println(context.Cause(ctx))
In some situations, you might want to collect two or more errors before returning to the caller.
The new function errors.Join()
expects a list of errors and joins them into a single error value.
// first error
err1 := errors.New("first error: out of luck")
// try to fix the error, fail again
err2 := errors.New("second error: failed fixing first error")
// try to clean up after the errors, get another error
err3 := errors.New("third error: cleanup failed")
// combine all the errors
err := errors.Join(err1, err2, err3)
fmt.Println(err)
// err Is() any of err1, err2, or err3
fmt.Println(errors.Is(err, err1))
fmt.Println(errors.Is(err, err2))
fmt.Println(errors.Is(err, err3))
// The errors do not unwrap through errors.Unwrap()
// errors.Unwrap() can only return a single error
// For multiple wrapped errors, it returns nil
fmt.Println(errors.Unwrap(err)) // <nil>
// Instead, a joined error provides a method "Unwrap() []error" that we can call
// But first, err must assert that it implments "Unwrap() []error"
e, ok := err.(interface{ Unwrap() []error })
if ok {
fmt.Println("Unwrapped:", e.Unwrap())
}
With Go 1.20, fmt.Errorf()
accepts multiple %w
verbs, which is an alternative way of joining errors.
// To wrap multiple errors, use fmt.Errorf
errWrapped := fmt.Errorf("multiple errors occurred: %w, %w, %w", err1, err2, err3)
fmt.Println(errWrapped)
Errors wrapped this way behave the same as those wrapped with Join()
.
See the full Go playground example.
Imagine that the Go compiler could take profiling information from application runs and optimize the application even more! This is now possible with
Profile-Guided Optimization (PGO),
at least as a preview. (Expect some rough edges.)
Here is how it works:
net/http/pprof
or other suitable ways of generating a profile. (If this is not possible, use a representative benchmark instead.)It is crucial to take the profiles from representative use cases. Typically, this means collecting them in the production environment. The more the profile represents the actual use of the application, the better the improvements are.
Read more in the official documentation: Profile-guided optimization - The Go Programming Language
Related to PGO is another new optimization feature:
Coverage profiling.
Code coverage profiles help determine if your code is sufficiently covered by unit tests. To generate a coverage profile report, you would run go test -coverprofile=...
followed by go tool cover...
.
Go 1.20 adds support for collecting coverage profiles from applications and integration tests. The process is slightly more involved than that for unit tests, but it can be broken down into three basic steps:
-cover
flag.More details on the official landing page: Coverage profiling support for integration tests - The Go Programming Language
What if your application does not exit on a regular basis? How can you collect profiling data then?
This is also taken care of. The new runtime/coverage
package contains functions for writing coverage profile data at runtime from long-running programs.
No, not entirely gone, but the pre-compiled archives of the standard library are not shipped anymore with the Go distribution, nor does it get pre-compiled into $GOROOT/pkg
. The packages of the standard library are now handled like any other package. The go command will build them as needed, and cache them in the build cache.
On systems without a C toolchain, cgo
is now disabled by default. When the go command does not find any suitable C compiler like clang
or gcc
, and when the environment variables CGO_ENABLED
and CC
are unset, the Go compiler assumes that CGO_ENABLED
is set to 0. Packages in the standard library that use cgo
will be compiled from alternate pure-Go code.
This change mainly affects net
, os/user
, and plugin
on Unix systems. On macOS and Windows, net
and os/user
do not use cgo
.
Type parameters (a.k.a generics, introduced in Go 1.18) use constraints to define the permissible type of arguments for a type parameter. A special case is the comparable
constraint. This constraint cannot be constructed with the usual methods for constructing a type constraint; hence it is a predeclared interface type.
The comparable
constraint shall represent all types for which the equal ("==
") and not-equal ("!=
") operations are defined. Until Go 1.20, however, this did not include any types that are considered comparable at compile time but for which the comparison may panic at runtime. This class of comparable types is called "non-strictly comparable" and includes interface types or composite types containing an interface type.
Go 1.20 now allows using such no-strictly comparable types for instantiating a type parameter constrained by comparable
.
Example:
func compare[T comparable](a, b T) bool {
if a == b {
return true
}
return false
}
func main() {
var n, m int
fmt.Println(compare(n, m))
var i1, i2 any
fmt.Println(compare(i1, i2))
}
Try it in the playground (as long as Go 1.19 can still be selected). With Go 1.20 or newer, both calls to compare()
return true. Switch the playground to Go 1.19 and the build will fail with this error message:
./prog.go:18:21: any does not implement comparable
Go build failed.
Prior to Go 1.20, the global random number generator in math/rand
received a deterministic seed value at startup. To get a random seed at every program start, the usual "trick" was to use the current unix timestamp as the seed value:
rand.Seed(time.Now().UnixNano())
This clumsy seeding is not necessary anymore. The global random number generator of math/rand
is now automatically seeded with a random value.
To force the old behavior, call rand.Seed(1)
at startup, or set GODEBUG=randautoseed=0
in the application's environment.
If you need a deterministic result sequence from the rand
functions, do not use the global random number generator. Instead, create a new generator:
rand.New(NewSource(seed))
(where seed
is a manually chosen integer value.) See func Seed for more information.
Also, the top-level Read
function has been deprecated. The release notes suggest using crypto/rand.Read
instead.
While the Go 1.20 release candidates were available, blog articles started discussing a new package called arena
that was supposed to be announced as an experimental feature for Go 1.20.
The arena package allows to create "memory arenas", areas in RAM that the garbage collector would ignore. The goal was to reduce pressure on the garbage collector for certain kinds of high-frequency allocations inside performance-critical paths. Inside an arena, code can manually allocate new objects of diverse size and lifetime. When work is finished, the code can free the whole arena and leave one single memory area for the garbage collector to clean up. If you think "C", yes, this sounds similar to the good old malloc
and free
, with a bit more memory safety through a tool called "address sanitizer" but still problematic enough to warrant the removal of the experiment from the Go1.20 documentation.
If you are curious, you can dig through the proposal and its discussion thread.
Converting a slice to an array is now as easy as:
s := []int{1, 2, 3, 4, 5}
a := [5]int(s)
Caveat: The slice may be longer than the array, but it must not be shorter, or the conversion panics. The length of an array must be known at compile time, so you cannot do something like, a := [len(s)]int(s)
. Note that the actual length is relevant here, not the capacity of the slice.
A net/http.ResponseWriter
can implement optional interfaces, such as Flusher
or Hijacker
. This way, a Web server can provide additional functionality to the ResponseWriter. On the downside, these interfaces are not discoverable and inelegant.
This example is from the documentation of type Hijacker
:
func main() {
http.HandleFunc("/hijack", func(w http.ResponseWriter, r *http.Request) {
hj, ok := w.(http.Hijacker)
if !ok {
http.Error(w, "webserver doesn't support hijacking", http.StatusInternalServerError)
return
}
conn, bufrw, err := hj.Hijack()
// ... check err and use the hijacked connection ...
The new net/http.ResponseController
makes those interface methods straightforward to use.
A ResponseController
is created from a ResponseWriter
passed to the ServeHTTP()
method. It supplies Flush()
and Hijack()
methods that call the respective interface method if the ResponseWriter
implements that interface, or return an error otherwise.
func main() {
http.HandleFunc("/hijack", func(w http.ResponseWriter, r *http.Request) {
c := http.NewResponseController(w)
conn, bufrw, err := c.Hijack()
// ... check err and use the hijacked connection ...
Go 1.20 adds a new crypto/ecdh
package to provide support for Elliptic Curve Diffie-Hellman key exchanges over NIST curves and Curve25519.
This package is recommended over the lower-level functionality in crypto/elliptic
for ECDH. For more advanced use cases, the Go team recommends using third-party modules.
Only good news here:
Lots of interesting updates and changes! I really can't say what's my favorite one. Time will tell.
Be sure also to visit the official announcement on the Go blog, and definitely check out the full list of changes in the Go 1.20 Release Notes.
Happy coding! ʕ◔ϖ◔ʔ
Applied Go Courses helps you get up to speed with Go without friction. Our flagship product, Master Go, is an online course with concise and intuitive lectures and many practices and exercises. Rich graphic visualizations make complex topics easy to understand and remember. Lectures are short and numerous to save your precious time and serve as quick reference material after the course. Learn more at https://appliedgo.com.
Walnut Photo by Ulrike Leone from Pixabay
Update 2023-02-04: Clarified the status and use of the memory arena feature. Removed the ?v=gotip
query from playground links because the playground's default version is now Go1.20.
Categories: : Releases