In this article, I will uncover the newest features introduced in Go 1.21, focusing on the updates and enhancements that you will find highly practical in your real-world experiences.
What if we gather information about how our Go code is used in production and, based on that, optimize the Go code specifically for our application?! PGO is focused on that task.
To begin, you'll need to collect a profile from your production environment, and you can do this with the help of pproof (https://pkg.go.dev/net/http/pprof). I will delve into this process more extensively in my upcoming article.
When we have collected a profile in a file named default.pgo, we need to put it in the root of our project (I advise keeping the file in the git repository with the rest of the project files). When go build is subsequently called, Go itself will apply the profile when building the binary of our application.
├── main.go
└── default.pgo
go build
If you want to place the profile in another location of your project, you can run go build with the profile location specified:
go build -pgo=/profiles/my.pprof
It should be noted that PGO is enabled by default, so if you need to build the application without PGO, use the -pgo=off
parameter:
go build -pgo=off
We’ve measured the impact of PGO on a wide set of Go programs and see performance improvements of 2-7%.
In Go, the built-in functions "min" and "max" are designed to determine the smallest and largest values, respectively, among a fixed number of arguments that should be of ordered types. It's important to note that these functions must have at least one argument.
The same type rules that are applicable to operators also extend to these functions. For ordered arguments x and y, it is valid to call "min(x, y)" if the addition "x + y" is a valid operation. The type of the result returned by "min(x, y)" matches the type of "x + y," and the same principle holds for the "max" function.
Additionally, if all the arguments passed to these functions are constants, the result returned by the functions will also be a constant value.
var x, y int
m := min(x) // m == x
m := min(x, y) // m is the smaller of x and y
m := max(x, y, 10) // m is the larger of x and y but at least 10
c := max(1, 2.0, 10) // c == 10.0 (floating-point kind)
f := max(0, float32(x)) // type of f is float32
var s []string
_ = min(s...) // invalid: slice arguments are not permitted
t := max("", "foo", "bar") // t == "foo" (string kind)
The "clear" function, built into Go, takes as its argument a map, slice, or type parameter type, and its role is to wipe out or reset all elements contained within the provided data structure.
func clear[T ~[]Type | ~map[Type]Type1](t T)
package main
import "fmt"
func main() {
mp := make(map[int]int)
mp[1] = 1
mp[2] = 2
mp[3] = 3
clear(mp)
// here we got the empty map: map[]
fmt.Println(mp)
sl := make([]int, 0)
sl = append(sl, 1)
sl = append(sl, 2)
sl = append(sl, 3)
clear(sl)
// here we got the slice with zeroes: [0 0 0]
fmt.Println(sl)
}
The clear built-in function clears maps and slices. For maps, clear deletes all entries, resulting in an empty map. For slices, clear sets all elements up to the length of the slice to the zero value of the respective element type. If the argument type is a type parameter, the type parameter's type set must contain only map or slice types, and clear performs the operation implied by the type argument.
A small quiz - what will this function print in Go < 1.21?
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println(i)
}()
}
wg.Wait()
}
Let’s check the output of the code:
10
10
10
10
10
10
10
10
10
10
This happened because we captured the loop variable i
. We can fix it, adding i := i
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
i := i
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println(i)
}()
}
wg.Wait()
}
Then we got the expected output:
3
2
5
4
9
8
7
6
0
1
In Go 1.21, we will get the same behavior by default, but also if we can add an environment variable:
GOEXPERIMENT=loopvar
We will get the expected output from the code:
3
2
5
4
9
8
7
6
0
1
Developers of the language want to eliminate this behavior in future versions of the language, as it is non-obvious.
Useful packages were added to the standard library in Go 1.21:
New log/slog package for structured logging.
This logger has a lot of great features:
Different levels of logging
Grouping logs by attributes
Passing attributes and values: slog.Info("hello", slog.Int("count", 3))
Logging with context
Log handlers
New slices package for common operations on slices of any element type. This includes:
New maps package for common operations on maps of any key or element type. Features:
clone of map (This is a shallow clone: the new keys and values are set using ordinary assignment)
delete elements by closure function
checking if two maps are equal
copy keys with values from one map to another
New cmp package with new utilities for comparing ordered values.
Enjoy the new language features!
Cheers!