What Is New in Go 1.21
Go 1.21 brings significant enhancements to the toolchain, standard library, and language itself. This release focuses on improving developer productivity, code clarity, and performance.
| Category | Key Changes |
|---|---|
| Language Changes | New built-in functions (min, max, clear), type inference for generic functions, and improved type checking. |
| Tooling | Structured profile-guided optimization (PGO), improved linker, and better backtrace information. |
| Standard Library | New slices, maps, and cmp packages. Updates to the log/slog package and HTTP routing. |
| Ports | Support for WASI preview1 and FreeBSD on RISC-V (riscv64). |
| Performance | Compiler optimizations, memory management improvements, and faster builds. |
What new built-in functions were added?
Go 1.21 introduces three new built-in functions: min, max, and clear. The min and max functions compare ordered values, returning the smallest or largest argument, respectively. This is handy for writing concise bounds-checking and comparison logic without helper functions.
The clear function removes all elements from a map or sets all elements of a slice to the zero value. For maps, it's equivalent to a loop that deletes every key. For slices, it zeroes the elements from index 0 to len(s)-1. This provides a clear and efficient way to reset these data structures.
// Example using the new built-ins
m := map[string]int{"a": 1, "b": 2}
clear(m) // m is now an empty map: map[]
s := []int{1, 2, 3, 4}
clear(s) // s is now []int{0, 0, 0, 0}
x := min(10, 5, 25) // x == 5
y := max(10, 5, 25) // y == 25
How does Profile-Guided Optimization work now?
Profile-Guided Optimization (PGO) is now fully integrated and stable in Go 1.21. You provide the compiler with a CPU profile from a previous run of your application, and it uses that data to guide its optimization decisions. This typically results in performance improvements of 2-7% for real-world workloads.
In practice, you build your binary with the -pgo flag pointing to a .pprof file. The compiler analyzes hot code paths and inlining decisions based on actual usage patterns, not just static analysis. This matters because it tailors optimizations to your specific application's behavior.
# Build with PGO
go build -pgo=auto cmd/myapp
# With auto, the compiler looks for a default.pgo file in the main package directory
# Or specify a specific profile
go build -pgo=./cpu.pprof cmd/myapp
What are the new generic slices and maps packages?
The new slices and maps packages provide generic functions for working with slices and maps of any element type. They eliminate the need to write repetitive boilerplate code for common operations like sorting, searching, comparing, and manipulating collections.
The slices package includes functions for cloning, sorting, binary search, and checking for containment. The maps package offers utilities for copying, retrieving keys, and comparing maps. These packages feel like a natural extension of the language's generic capabilities introduced in earlier versions.
import "slices"
func main() {
s := []int{3, 1, 4, 1, 5}
sorted := slices.Sort(s) // [1, 1, 3, 4, 5]
hasFive := slices.Contains(s, 5) // true
cloned := slices.Clone(s)
}
How has the logging improved with slog?
The log/slog package, which was experimental in Go 1.20, is now stable in Go 1.21. It provides structured logging with levels, key-value attributes, and pluggable handlers for output formats like JSON and text. This is a significant upgrade from the standard log package for production systems.
You can easily attach contextual information to log lines, which is crucial for debugging complex applications. Handlers can be customized to output logs in different formats, making it easier to integrate with log aggregation systems. The API is designed to be both simple for basic use cases and extensible for advanced needs.
import "log/slog"
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
logger.Info("user logged in",
"user_id", 12345,
"ip", "192.168.1.1",
)
// Outputs: {"time":"...","level":"INFO","msg":"user logged in","user_id":12345,"ip":"192.168.1.1"}
FAQ
Is the `slog` package backward compatible with the old `log` package?
Not directly. The `log/slog` package has a different API focused on structured logging. However, you can use the `slog.NewLogLogger` function to create a `slog.Logger` that implements the `log.Logger` interface, allowing for a gradual migration.
Do I have to use PGO to get good performance?
No. The standard compiler optimizations are still excellent. PGO provides an additional, optional performance boost by tailoring optimizations to your specific application's execution profile.
What happens if I call `clear` on a nil map or slice?
Calling `clear` on a nil map is a no-op-it does nothing. Calling `clear` on a nil slice panics, just like any other operation that tries to access the length of a nil slice.
Are the new `slices` and `maps` packages meant to replace the standard ways of working with them?
They are meant to complement them. For simple operations, using language built-ins is fine. For more complex operations like sorting or deep comparisons, the new generic packages are the recommended approach.
Does the improved linker make a noticeable difference in build times?
Yes, especially for larger applications. The linker improvements, combined with other optimizations, contribute to faster overall build times, which is a big win for developer productivity in large codebases.