paint-brush
Go 1.21: Taking Your Go Apps to the Next Levelby@therealone
205 reads

Go 1.21: Taking Your Go Apps to the Next Level

by Denis LarionovOctober 19th, 2023
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Discover Go 1.21's cutting-edge features: profile-guided optimization, min-max functions, clear function, loop variable enhancement, and more.
featured image - Go 1.21: Taking Your Go Apps to the Next Level
Denis Larionov HackerNoon profile picture

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.

The Profile Guided Optimization (PGO)

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%.

Min and max functions

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)

Clear Function

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.

Loop Variable Capture Improvement

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.

Standard library additions

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:

    • Sorting functions that are generally faster and more ergonomic than the sort package.
    • binary search function
    • max, min functions
    • function that replaces values of particular indexes
    • reverse function
    • finding index of the value or checking if the value contains in slice
    • delete function
    • function that makes elements of slice unique
    • comparing slices function
    • Grow function - to increase slice's capacity


  • 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!