This series aims to sort out Go's release notes and development history to understand Go's language design ideas more deeply.
/doc/go1.22
Go 1.22 Changes worth paying attention to:
-
for
Circular improvement: Loop variables create new instances at each iteration to avoid closure sharing problems;for range
It is now supported to traverse integers. -
Workspace improvements :
go work
supportvendor
Directory, allowing workspaces to manage dependencies in a unified manner. -
vet
Tool enhancement: Addeddefer
In the statementWarning of wrong usage.
- Runtime optimization: Improve program performance and memory efficiency by improving the storage method of Garbage Collection metadata.
- Compiler optimization: Improved the effect of Profile-guided Optimization (PGO) and enhanced inlining strategy.
-
New
math/rand/v2
Bag: Newmath/rand/v2
package, providing a more modern, faster pseudo-random number generator and a more Go-friendly API. -
New
go/version
Bag: Provides functionality for verifying and comparing Go version strings. -
Enhanced
net/http
routing: Standard librarynet/
Supports more powerful routing patterns, including HTTP method matching and path parameters (wildcards).
Here are some discussions worth developing:
for
Two important improvements to the cycle
Go 1.22for
Two important improvements to the loop are made: semantic changes of loop variables and integersrange
support.
1. Change of loop variable scope
Before Go 1.22,for
Variables declared by loop (e.g.for i, v := range slice
In-housei
andv
) will only be created once. In each iteration, the values of these variables are updated. This often leads to a classic bug: if a goroutine started inside a loop references these loop variables, they may accidentally share the final value of the same variable, rather than capturing the value at each iteration.
Consider the following Go 1.21 and previous codes:
package main
import (
"fmt"
"time"
)
func main() {
s := []string{"a", "b", "c"}
for _, v := range s {
go func() {
(v) // Expected output a, b, c
}()
}
(1 * ) // Wait for goroutine to execute
}
In Go 1.21 and earlier, this code is likely to be output three timesc
, because all goroutines capture the same variablev
, and when goroutine is actually executed, the loop has ended,v
The value of the last iteration"c"
。
To solve this problem, developers usually need to explicitly create a new variable inside the loop to capture the value of the current iteration:
// Go 1.21 and previous repair methods
for _, v := range s {
v := v // Create a new v, shadowing the outer layer of v
go func() {
(v)
}()
}
Starting from Go 1.22, the language specification has been modified:Each loop iteration creates a new loop variable. This means that in Go 1.22, no modification is required, the first example above works as expected, outputa
, b
, c
(The order is indefinite, because goroutine is executed concurrently). This change greatly reduces the possibility of errors due to cyclic variable sharing.
2. for range
Support integers
Go 1.22 introduces a convenient syntactic sugar:for range
It can now be used directly in integer types.for i := range n
The form is equivalent tofor i := 0; i < n; i++
. This makes writing simple count loops simpler.
For example, to print 10 to 1 in reverse order:
package main
import "fmt"
func main() {
// Go 1.22 added syntax
for i := range 10 {
(10 - i)
}
("go1.22 has lift-off!")
// Equivalent Go 1.21 and previous writing methods
// for i := 0; i < 10; i++ {
// (10 - i)
// }
}
This new feature simplifies code and improves readability.
In addition, Go 1.22 also includes an experimental language feature preview: support for function productionrange
iterators. You can set environment variablesGOEXPERIMENT=rangefunc
to enable this feature, but this is still in the experimental stage and may change in future releases.
Workspaces supportvendor
Table of contents
Go 1.22 has enhanced support for Workspaces mode and introducedvendor
Integration of the catalog.
In Go 1.21 and before,vendor
Directories are module-level features. Each module can have its ownvendor
Directory, storing the dependencies of this module. However, when using the Go workspace to manage multiple interrelated modules, there is no unifiedvendor
mechanism. Developers may need to execute individually under each modulego mod vendor
, or rely on the Go toolchain to automatically find the dependencies of each module.
Go 1.22 introducedgo work vendor
Order. When you run this command in the root of the workspace, it creates a top-levelvendor
directory and put all modules in the workspaceAll dependenciesCollected into this directory.
After that, when you execute build commands in the workspace (such asgo build
, go test
) if this top level existsvendor
Directory, Go toolchain will be used by default-mod=vendor
Sign, priority from thisvendor
Find dependencies in the directory, rather than downloading or searching for localGOPATH
or module cache.
This brings several benefits:
-
Dependency isolation and consistency: Ensure that all modules in the entire workspace are in the same set of
vendor
Fixed dependency versions that enhance the determinism and reproducibility of the build. -
Simplify offline construction: Only a top-notch
vendor
Directory can support offline construction of the entire workspace. -
Unified management: No need to maintain the respective one in each submodule
vendor
Table of contents.
It should be noted that the workspacevendor
Directory and single modulevendor
Directories are different. If the root directory of the workspace happens to be the root directory of one of the modules, then thevendor
Subdirectories or serve the entire workspace (bygo work vendor
Created), or serve the module itself (bygo mod vendor
Created), but cannot serve both.
In addition, Go 1.22'sgo
There are some other changes to the command:
- In the old
GOPATH
In mode (i.e. settingGO111MODULE=off
),go get
The command is no longer supported. But other build commandsgo build
andgo test
Will still be supported indefinitelyGOPATH
project. -
go mod init
No more attempts from other package management tools (e.g.) import dependencies in the configuration file.
-
go test -cover
Now output coverage summary (usually 0.0%) for packages that do not have their own test files but are overwritten, rather than the previous one[no test files]
hint. - If the build command requires calling the external C linker, but
cgo
Not enabled, an error will be reported now. Because Go runtime requirescgo
Support to ensure compatibility with libraries added by C linker.
vet
Tool pairdefer
New warning
Go 1.22vet
The tool has added a check to identifydefer
The statement in the rightCommon misuses.
Consider the following code snippet, whose purpose is to record the execution time when the function exits:
package main
import (
"log"
"time"
)
func operation() {
t := ()
// Common error usage:
defer ((t)) // vet will warn this in Go 1.22
// Simulate some time-consuming operations
(100 * )
}
func main() {
operation()
}
Many developers expectdefer ((t))
Will be hereoperation
Calculate when the function is about to return(t)
, thus obtainingoperation
The exact execution time of the function. However,defer
This is not the case with the working mechanism.
defer
The statement will follow itFunction CallsDelayed to includedefer
The function will be executed before it is returned. but,The parameter of the function call isdefer
The statement is evaluated immediately and saved when it is executed. 。
Therefore, indefer ((t))
When this line of code is executed:
-
(t)
quiltCall now. becauset
Just set to()
,at this time(t)
The result is almost 0 (or a very small value). -
The function and its (almost 0) parameters are registered as a delayed call.
- when
operation
When the function ends, it is postponedThe function is executed and prints out the
defer
A very small time difference has been calculated when the statement is executed.
This is obviously not what we want.vet
The tool now warns this pattern because it is almost always wrong.
The right thing to do is to ensure(t)
In the delay functionWhen actually executedJust called. This is usually achieved by a closure (anonymous function):
package main
import (
"log"
"time"
)
func operationCorrect() {
t := ()
// Correct usage:
defer func() {
// (t) is called inside the function body of defer
// This ensures that it calculates the time difference when operationCorrect is about to return
((t))
}()
// Simulate some time-consuming operations
(100 * )
}
func main() {
operationCorrect() // Output a value close to 100ms
}
In this correct version,defer
An anonymous function is followed byfunc() { ... }
. The anonymous function itself is deferred execution. whenoperationCorrect
When it is about to return, this anonymous function is called, and at this time its internal(t)
It will be executed, so as to correctly calculate thet
The total time it is assigned to the function returns.
vet
This new check helps developers avoid this commondefer
Trap, ensure the correctness of timing logic.
Runtime Optimization: Improved GC Metadata Layout
The Go 1.22 runtime has implemented an optimization that changes the storage of type-based metadata required for Garbage Collection (GC). Now, this metadata is stored closer to the object itself on the heap.
This change brings two main benefits:
- Performance improvement: By making GC metadata physically closer to the object in memory, the locality of CPU cache is utilized. When GC needs to access the metadata of an object, this data is more likely to be already in the CPU cache, reducing the latency of reading data from main memory. This increases the CPU performance (latency or throughput) of Go programs by 1-3%.
- Reduced memory overhead: By reorganizing metadata, the runtime can better deduplicate redundant metadata information. For most Go programs, this reduces memory overhead by about 1%.
To understand this change, we can make a simple analogy (note that this is just a conceptual model that helps understand, not an exact representation of memory layout):
Assume that in Go 1.21, the heap memory layout might look like this:
[Object A Header] [Object A Data...] [Object B Header] [Object B Data...]
| |
+-----------------+ +-----------------+
| |
V V
[Metadata Area: Type Info for A, ...] [Metadata Area: Type Info for B, ...]
GC needs to jump between the object header and the metadata area that may be far apart.
In Go 1.22, the layout may be closer to this:
[Object A Header | Metadata for A] [Object A Data...] [Object B Header | Metadata for B] [Object B Data...]
Metadata is close to the object header, improving access efficiency. At the same time, if multiple objects share the same metadata, the runtime can manage these shared information more effectively, reducing the overall memory footprint.
However, this optimization also brings a potential side effect:Changes in memory alignment 。
Prior to this change, Go's memory allocator tended to allocate objects on 16-byte (or higher) aligned memory addresses. However, the optimized metadata layout adjusts the internal size class boundary of the memory allocator. Therefore, some objects may now guarantee only 8 bytes of alignment, rather than the previous 16 bytes.
For the vast majority of pure Go codes, this change has no effect. However, if your code contains handwritten assembly code and these assembly codes rely on Go object addresses with an alignment guarantee of more than 8 bytes (for example, using SIMD instructions that require a 16-byte alignment address), these codes may fail under Go 1.22.
The Go team expects this to be very rare. But if you do encounter problems, you can use them temporarilyGOEXPERIMENT=noallocheaders
Build the program to restore old metadata layout and alignment behavior. However, this is just a temporary solution, and the maintainers of the package should update their assembly code as soon as possible, removing the assumptions about specific memory alignments, because thisGOEXPERIMENT
The flag will be removed in a future version.
Compiler optimization: stronger PGO and inline
The Go 1.22 compiler has made progress in optimization, especially enhanced Profile-guided Optimization (PGO) and inlining strategies.
1. PGO effect enhancement
PGO is a compiler optimization technology that uses real execution data (profiles) during program runtime to guide the compilation process and make better decisions. In Go 1.22, a key improvement in PGO is the ability toDevirtualizationA higher proportion of interface method calls.
Devirtualization refers to the compiler being able to determine the specific type that an interface variable actually points to at a call point, thereby replacing the method call that originally needed to search through the interface (dynamic dispatch) with a direct call to the specific type method (static dispatch). Direct calls are usually faster than interface calls.
Imagine a code like this:
type Writer interface {
Write([]byte) (int, error)
}
func writeData(w Writer, data []byte) {
(data) // This is an interface call
}
type fileWriter struct { /* ... */ }
func (fw *fileWriter) Write(p []byte) (int, error) { /* ... */ }
func main() {
// ...
f := &fileWriter{}
// Assuming that PGO data shows that writeData is always or often called by fileWriter
writeData(f, someData)
}
If the PGO data indicateswriteData
in the functionw
Variables are mostly runtime*fileWriter
Types, Go 1.22 compilers are more likely to(data)
This interface call is optimized to(data)
direct call to improve performance.
Thanks to this stronger devirtualization capability and other PGO improvements, most Go programs can now observe after enabling PGO2% to 14%runtime performance improvement.
2. Improved inline strategy
Inline is an operation that replaces function calls with the function body itself, which can eliminate the overhead of function calls and create opportunities for other optimizations (such as constant propagation and dead code elimination).
The Go 1.22 compiler is now betterInterleave devirtualization and inline. This means that even interface method calls may be more likely to be inlined and further optimized for performance after being deviated through PGO and become direct calls.
In addition, Go 1.22 also includes aExperimental enhanced inline. This new inline uses heuristics to decide more intelligently whether to inline. It tends to inline at a call point that is considered "important" (e.g. inside a loop) and at a call point that is considered "unimportant" (e.g.panic
on the path) reduces inline to balance performance improvements and code volume growth.
You can set environment variablesGOEXPERIMENT=newinliner
To enable this new experimental inline. Related discussions and feedback can be found in/golang/go/issues/61502Found in.
Enhancednet/http
Routing mode
Go 1.22 for the standard librarynet/
Significantly enhanced to make its routing patterns more expressive, introducing support for HTTP methods and path parameters (wildcards).
Before this,The routing function is very basic and can basically only match based on the URL path prefix. This makes it possible for developers to introduce third-party routing libraries when implementing RESTful APIs or more complex routing logic.
The improvements in Go 1.22 have greatly enhanced the routing capabilities of the standard library:
1. HTTP method matching
HTTP methods can now be specified when registering a handler.
package main
import (
"fmt"
"net/http"
)
func main() {
mux := ()
// Only match POST requests to /items/create
("POST /items/create", func(w , r *) {
(w, "Create item")
})
// Match all methods /items/
("/items/", func(w , r *) {
(w, "Default item handler for method %s\n", )
})
// (":8080", mux)
}
-
POST /items/create
Will only matchPOST
Method request. - Patterns with methods have higher priority than general patterns without methods. For example, a
POST
Request to/items/create
Will be processed by the first processor, and oneGET
Request to/items/create
It will fall back to the match/items/
processor (if present and match). - Special circumstances: Register
GET
The processor of the method will also be automaticallyHEAD
Request to register the same processor.
2. Path parameters (Wildcards)
Can be used in mode{}
To define path parameters (also called path variables or wildcards).
package main
import (
"fmt"
"net/http"
)
func main() {
mux := ()
// Matches such as /items/123, /items/abc, etc.
// {id} Match a segment in the path (segment)
("/items/{id}", func(w , r *) {
// Get the actual matching value through ("id")
itemID := ("id")
(w, "Get item with ID: %s\n", itemID)
})
// Match like /files/a/b/
// {path...} must be at the end, matching all remaining path segments
("/files/{path...}", func(w , r *) {
filePath := ("path")
(w, "Accessing file path: %s\n", filePath)
})
// (":8080", mux)
}
-
{name}
A wildcard of form matches a single segment in the URL path. -
{name...}
A wildcard of the form must appear at the end of the pattern, which will match all remaining path segments after that point. - Available
("name")
Get the actual value matched by the wildcard in the processor function.
3. Exact match with suffix slashes
- As before,
/
Ending mode (e.g./static/
) will match all paths prefixed with this. - If you want to match exactly a path ending with a slash (rather than as a prefix), you can add it at the end
{$}
,For example/exact/match/{$}
Will only match/exact/match/
It won't match/exact/match/foo
。
4. Priority Rules
When two patterns may match the same request (pattern overlap),More specificThe mode is preferred. If there is no clear which one is more specific, then the pattern conflicts (it will be panic when registering). This rule promotes the previous priority rules and ensures that the registration order does not affect the final matching result.
For example:
-
POST /items/{id}
Compare/items/{id}
More specific (because it specifies the method). -
/items/specific
Compare/items/{id}
More specific (because it contains a literal segment instead of a wildcard). -
/a/{x}/b
and/a/{y}/c
There is no clear which is more specific if they may match the same request path (e.g./a/foo/b
and/a/foo/c
No conflict, but/a/{z}
and/a/b
may conflict), depending on the implementation, but usually/a/b
Will take precedence/a/{z}
。
5. Backward compatibility
These changes undermine backward compatibility in some ways:
- Include
{
and}
The path to this will now be parsed into a wildcard pattern, behaving differently than before. - The handling of escaped characters in the path has also been improved, which may lead to behavioral differences.
To help smooth the transition, you can set itGODEBUG
Environment variables to restore old behavior:
export GODEBUG=httpmuxgo121=1
Overall, Go 1.22 isnet/
The enhancement greatly improves the standard library's ability to develop web, and reduces dependence on third-party routing libraries.