Location>code7788 >text

What are the notable changes to Go 1.22 compared to Go 1.21?

Popularity:481 ℃/2025-05-05 09:11:21

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:

  1. forCircular improvement: Loop variables create new instances at each iteration to avoid closure sharing problems;for rangeIt is now supported to traverse integers.
  2. Workspace improvementsgo worksupportvendorDirectory, allowing workspaces to manage dependencies in a unified manner.
  3. vetTool enhancement: AddeddeferIn the statementWarning of wrong usage.
  4. Runtime optimization: Improve program performance and memory efficiency by improving the storage method of Garbage Collection metadata.
  5. Compiler optimization: Improved the effect of Profile-guided Optimization (PGO) and enhanced inlining strategy.
  6. Newmath/rand/v2Bag: Newmath/rand/v2package, providing a more modern, faster pseudo-random number generator and a more Go-friendly API.
  7. Newgo/versionBag: Provides functionality for verifying and comparing Go version strings.
  8. Enhancednet/httprouting: Standard librarynet/Supports more powerful routing patterns, including HTTP method matching and path parameters (wildcards).

Here are some discussions worth developing:

forTwo important improvements to the cycle

Go 1.22forTwo important improvements to the loop are made: semantic changes of loop variables and integersrangesupport.

1. Change of loop variable scope

Before Go 1.22,forVariables declared by loop (e.g.for i, v := range sliceIn-houseiandv) 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,vThe 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 rangeSupport integers

Go 1.22 introduces a convenient syntactic sugar:for rangeIt can now be used directly in integer types.for i := range nThe 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 productionrangeiterators. You can set environment variablesGOEXPERIMENT=rangefuncto enable this feature, but this is still in the experimental stage and may change in future releases.

Workspaces supportvendorTable of contents

Go 1.22 has enhanced support for Workspaces mode and introducedvendorIntegration of the catalog.

In Go 1.21 and before,vendorDirectories are module-level features. Each module can have its ownvendorDirectory, storing the dependencies of this module. However, when using the Go workspace to manage multiple interrelated modules, there is no unifiedvendormechanism. 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 vendorOrder. When you run this command in the root of the workspace, it creates a top-levelvendordirectory 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 existsvendorDirectory, Go toolchain will be used by default-mod=vendorSign, priority from thisvendorFind dependencies in the directory, rather than downloading or searching for localGOPATHor module cache.

This brings several benefits:

  1. Dependency isolation and consistency: Ensure that all modules in the entire workspace are in the same set ofvendorFixed dependency versions that enhance the determinism and reproducibility of the build.
  2. Simplify offline construction: Only a top-notchvendorDirectory can support offline construction of the entire workspace.
  3. Unified management: No need to maintain the respective one in each submodulevendorTable of contents.

It should be noted that the workspacevendorDirectory and single modulevendorDirectories are different. If the root directory of the workspace happens to be the root directory of one of the modules, then thevendorSubdirectories or serve the entire workspace (bygo work vendorCreated), or serve the module itself (bygo mod vendorCreated), but cannot serve both.

In addition, Go 1.22'sgoThere are some other changes to the command:

  • In the oldGOPATHIn mode (i.e. settingGO111MODULE=off),go getThe command is no longer supported. But other build commandsgo buildandgo testWill still be supported indefinitelyGOPATHproject.
  • go mod initNo more attempts from other package management tools (e.g.) import dependencies in the configuration file.
  • go test -coverNow 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, butcgoNot enabled, an error will be reported now. Because Go runtime requirescgoSupport to ensure compatibility with libraries added by C linker.

vetTool pairdefer New warning

Go 1.22vetThe tool has added a check to identifydeferThe 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 hereoperationCalculate when the function is about to return(t), thus obtainingoperationThe exact execution time of the function. However,deferThis is not the case with the working mechanism.

deferThe statement will follow itFunction CallsDelayed to includedeferThe function will be executed before it is returned. but,The parameter of the function call isdeferThe statement is evaluated immediately and saved when it is executed.

Therefore, indefer ((t))When this line of code is executed:

  1. (t)quiltCall now. becausetJust set to(),at this time(t)The result is almost 0 (or a very small value).
  2. The function and its (almost 0) parameters are registered as a delayed call.
  3. whenoperationWhen the function ends, it is postponedThe function is executed and prints out thedeferA very small time difference has been calculated when the statement is executed.

This is obviously not what we want.vetThe 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,deferAn anonymous function is followed byfunc() { ... }. The anonymous function itself is deferred execution. whenoperationCorrectWhen 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 thetThe total time it is assigned to the function returns.

vetThis new check helps developers avoid this commondeferTrap, 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:

  1. 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%.
  2. 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=noallocheadersBuild 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 thisGOEXPERIMENTThe 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 indicateswriteDatain the functionwVariables are mostly runtime*fileWriterTypes, 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.panicon the path) reduces inline to balance performance improvements and code volume growth.

You can set environment variablesGOEXPERIMENT=newinlinerTo enable this new experimental inline. Related discussions and feedback can be found in/golang/go/issues/61502Found in.

Enhancednet/httpRouting 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/createWill only matchPOSTMethod request.
  • Patterns with methods have higher priority than general patterns without methods. For example, aPOSTRequest to/items/createWill be processed by the first processor, and oneGETRequest to/items/createIt will fall back to the match/items/processor (if present and match).
  • Special circumstances: RegisterGETThe processor of the method will also be automaticallyHEADRequest 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/specificCompare/items/{id}More specific (because it contains a literal segment instead of a wildcard).
  • /a/{x}/band/a/{y}/cThere is no clear which is more specific if they may match the same request path (e.g./a/foo/band/a/foo/cNo conflict, but/a/{z}and/a/bmay conflict), depending on the implementation, but usually/a/bWill 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 itGODEBUGEnvironment 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.