golang

On March 15, 2022, the Go team officially announced the release of Go 1.18 on the official blog. The addition of generics makes Go 1.18 another milestone release after Go 1.0 (the first official release), Go 1.5 (implementation of bootstrap, de-C code, new version of GC), and Go 1.11 (introduction of Go module) versions.

Generics are the biggest syntax feature change in the Go language since it was open-sourced, and the changes and impact have been so great that the Go core team has delayed the official release of Go 1.18 by a month despite their efforts. But the good news is that Go 1.18 with the addition of the generic syntax continues to maintain Go1 compatibility, which in itself is a victory for the Go team and equally a blessing for the Go community.

Compared to previous versions, Go 1.18 has been changed a lot and slightly more bugs. The good thing is that a month after the release, all kinds of noise have returned to quiet. At the time of writing, Go 1.18.1 has been released, fixing many issues, including of course some issues related to Go generics.

Let’s take a look at the noteworthy changes in Go 1.18 , the version I’m using here is Go 1.18.1.

We’ll start with generics.

1. Go syntax changes

1. Generics: the most complex Go syntax feature ever

In previous major Go releases, the Go syntax changes column was always sparse, or even passed over because there were no changes.

What’s more, from Go 1.0 to Go 1.17, there were only a few syntax changes.

  • Go version 1.1: add “method value” syntax.
  • Go 1.2: Full slice expression added: a[low: high: max].
  • Go 1.4: new for-range syntax of the form for range x {…}.
  • Go version 1.5: support for omitting the type of the key in a literal of type map (literal).
  • Go version 1.9: new syntax for type alias.
  • Go 1.13: add binary numeric literals starting with 0b or 0B, octal numeric literals starting with “0o” or “0O”, hexadecimal floating-point literals starting with 0x or 0X, as well as support for the use of numeric separators in numeric literals with the number separator “_” to improve readability.
  • Go version 1.17: support for conversion from slices to array pointers.

As we have seen, Go has had only a few of the above changes at the pure syntax level in a decade. The complexity of the generics introduced in Go 1.18 is more than the sum of the syntax changes in the above versions. Even Gopher, with years of experience in Go programming, feels like a “second learning curve” when faced with the new generic features. This is because Go generics are the most complex and difficult to read and understand syntax feature since the birth of Go. Of course, the complexity of generics is not only effective for Go, but also for other programming languages that have generic syntax features.

Also because of the complexity of generics, the Go team reserves the right in the release notes for Go 1.18 to introduce breaks to programs compiled in Go 1.18 in future releases as a result of fixing Go generic bugs. The Go team is of course committed to minimizing any such breakage, but cannot guarantee that such breakage will be zero.

Also, the Go 1.18 implementation of generics is not a full version and has many usage constraints. It is likely that these constraints will be phased out in subsequent versions of Go. And the implementation in Go 1.18 is consistent with the design document for Type Parameter Proposal has some differences, and Go officially recommends that the specification for the Go language prevails.

2. The main syntax points of generics

The last version of the Go generic technical proposal is dozens of pages long, and if we were to go into the details of it, it could be a booklet of its own. In this overview article, I will only briefly explain the main syntax points of Go generics. In future articles, we will dive into the syntactic details of generics and do a detailed analysis one by one.

The main syntax points of Go generics are actually mentioned in the official Go blog “Introduction to Go Generics”.

Generics add three new and important elements to the Go language.

  • New support for type parameters in functions and types.
  • Define interface types as collections of types, including interface types without methods.
  • Type derivation is supported, and in most cases, type arguments can be omitted when calling generic functions.

Let’s take a look at each of them.

type parameter

A type parameter is an (unqualified) type name declared in the receiver section of a function declaration, method declaration, or in the type parameter list of a type definition. The type parameter acts as a placeholder for an unknown type in the declaration, and is replaced by a type argument when a generic function or generic type is instantiated.

To give you a better understanding of how a type argument is declared and what it does, let’s compare the arguments of a normal function with the type argument of a generic function, using a function as an example.

We know that the list of arguments of a normal function looks like this.

1
func Foo(x, y aType, z anotherType)

Here, x, y, z are the names of the formal parameters (parameters), that is, variables, and aType, anotherType are the types of the formal parameters, that is, types.

Let’s look at the list of type parameters of generic functions.

1
func GenericFoo[P aConstraint, Q anotherConstraint](x,y P, z Q)

Here, P, Q are the names of type formal parameters, that is, types. aConstraint, anotherConstraint represent constraints on type parameters (constraint), which we can understand as a qualification of optional values of type parameters.

The constraint is what modifies the type parameter in the type parameter list. So what is a constraint? Let’s move on to the next section.

Constraint

A constraint specifies the conditions that a type argument must satisfy. If a type satisfies all the conditions specified by a constraint, then it is a legal type argument for the type formal parameter modified by the constraint.

In Go generics, we use the interface type to define constraints . For this reason, the definition of Go interface types is also extended so that we can declare both a collection of methods for the interface and a list of types that can be used as type real parameters.

The following is an example of a constraint definition and usage.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
ype C1 interface {
    ~int | ~int32
    M1()
}

type T struct{}
func (T) M1() {
}

type T1 int
func (T1) M1() {
}

func foo[P C1](t P)() {
}

func main() {
    var t1 T1
    foo(t1)
    var t T
    foo(t) // 编译器报错:T does not implement C1
}

In this code, C1 is the constraint we defined, which declares a method M1, and two types (~int | ~int32) that can be used as type real parameters. We see that the multiple type real reference types in the type list are separated by “|”.

In this code, we also define two custom types T and T1, both of which implement the M1 method, but the underlying type of type T is struct{}, while the underlying type of type T1 is int. This leads to the fact that although type T satisfies the set of methods of constraint C1, type T does not satisfy constraint C1 because the underlying type is not int or int32, which also will cause the foo(t) call to report an error at the compile stage. However, I would also like to advise you that it is best to define interface types that do constraints separately from those that do traditional interfaces, unless the constraint type really needs both a collection of methods and a list of types.

To make this extension to interface types better understood, Go introduced type sets to explain it all.

type inference and type specification

The call to foo(t1) by the main function in the above example takes advantage of both type-instantiation and type-inference.

foo is a generic function with a type reference P bound by C1 in its function declaration, and the process of initializing P with the type reference T1 is type inference. As you may notice, instead of using: fooT1, we omit the explicit initialization of P and use foo(t1), which is a convenience of Go type derivation. Automatic type derivation allows for a more natural style of writing code that calls generic functions.

Generic type

In addition to functions that can take type parameters and become “generic functions”, types can also have type parameters and become “generic types”, for example, the following code defines a vector generic type.

1
type Vector[T any] []T

This is a type definition with type parameters, which are located after the type name, again enclosed in square brackets. The parameter names in the type parameter list (e.g. T) can be referenced in the type definition body. Type parameters also have their own constraints, such as any in the code above.

In Go 1.18, any is an alias for interface{}, which is also a predefined identifier, and using any as a constraint on a type parameter means that there are no constraints.

Here is another definition of a generic type.

1
2
3
4
5
6
7
8
type Tree[T interface{}] struct {
    left, right *Tree[T]
    value       T
}

func (t *Tree[T]) Lookup(x T) *Tree[T] { ... }

var stringTree Tree[string]

In the above example, the generic type Tree stores the value of the type parameter T. A generic type can also have methods, such as Lookup in this example. Generic types can also have methods, such as Lookup in this example. In order to use a generic type, it must be instantiated, e.g. Tree[string] is an example of instantiating Tree with the type real parameter string.

Shortcomings of current generic implementations

Generics affect Go projects in every way, and it is indeed a bit difficult to implement all the features of generics in one release iteration cycle. As a result, the current implementation of Go generics in Go 1.18 is incomplete and has limitations, including the following according to the Go 1.18 release notes.

  • The Go compiler cannot handle type declarations in generic functions or methods, and the Go team hopes to provide support for this feature in a future release.

    1
    2
    3
    4
    5
    6
    
    func GenericsFoo[T any](s T) T {
        type bar int // type declarations inside generic functions are not currently supported
        var a bar
        println(a)
        return s
    }
    
  • The Go compiler does not support the predefined functions real, imag and complex for handling generic type real parameters. the Go team hopes to remove this restriction in a future release.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    package main
    
    import (
        "golang.org/x/exp/constraints"
    )
    
    func GenericsFoo[T constraints.Complex](s T) T {
        n := real(s) // s (variable of type T constrained by constraints.Complex) not supported as argument to real for go1.18 (see issue #50937
        println(n)
    
        i := complex(s, s) // invalid argument: arguments have type T, expected floating-point
        _ = i
        return s
    }
    
    func main() {
        var i = complex(1.0, 2.0) // 1+2i
        GenericsFoo(i)
    }
    
  • The Go compiler only supports calling method m on a valuex of argument type P, provided that: m is explicitly declared by P’s constraint interface. Similarly, method valuex.m and method expression P.m are only supported if m is explicitly declared by P. Even if all types in the set of P types implement m, calling m on x is not supported if m is not declared explicitly. The Go team hopes to remove this restriction in a future release.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    package main
    
    type C interface {
        T | T1 // T和T1都实现了M1方法
    }
    
    func GenericsFoo[P C](p P) {
        p.M1() // p.M1 undefined (type P has no field or method M1)
    }
    
    type T struct{}
    
    func (T) M1() {}
    
    type T1 struct{}
    
    func (T1) M1() {}
    
    func main() {
        GenericsFoo(T{})
    }
    
  • The Go compiler currently does not support access to a structure field x.f where x is the type parameter type, even if all types in the type set of the type parameter have a field f. The Go team may remove this restriction in a future release.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    package main
    
    type C interface {
        T | T1 // T和T1的类型定义中都包含名为Name的字段
    }
    
    func GenericsFoo[P C](p P) {
        _ = p.Name // p.Name undefined (type P has no field or method Name)
    }
    
    type T struct {
        Name string
    }
    
    type T1 struct {
        Name string
    }
    
    func main() {
        GenericsFoo(T{})
    }
    
  • The Go compiler currently does not allow type parameters or pointers to type parameters to be embedded in fields as structure types (unnamed fields). Similarly, embedding a type parameter in an interface type is not allowed. The Go team is currently unsure if these restrictions will be relaxed in future versions.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    package main
    
    type F[T any, P any] struct {
        Name string
        *T //embedded field type cannot be a (pointer to a) type parameter
        P // embedded field type cannot be a (pointer to a) type parameter
    }
    
    type MyInterface interface{}
    
    type GenericsInterface[I MyInterface] interface {
        M1()
        I // cannot embed a type parameter
    }
    
    func main() {
        var f F[string, string]
        _ = f
    }
    
  • The Go compiler does not support including an interface type with a non-empty set of methods in a union type definition that contains more than 1 type element. Whether this will be allowed in a future version, the Go team is currently unsure.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    package main
    
    type MyInterface interface {
        M1()
    }
    
    type GenericsInterface interface {
        ~int | MyInterface | float64 // cannot use main.MyInterface in union (main.MyInterface contains methods)
    }
    
    func main() {
    }
    

Another widespread concern is that type parameters are not supported in method declarations of ordinary types.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
package main

type F struct{}

func (F) M1[T any](t T){} // syntax error: method must have no type parameters

func main() {
    var f F[string]
    f.M1("hello")
}

However, this is not an implementation-level restriction, but rather the draft Go generalization technology is set up that way. It is uncertain whether the use of type parameters in methods will be supported in the future. However, the above problem can be “mitigated” by using generic types with type parameters.

A generic type can have its own method, and use the same type parameter as the type declaration in the receiver of the generic type’s method declaration, which can also be used in the method’s normal parameter list, as in the following example.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
package main

type F[T any] struct{}

func (F[T]) M1(t T) {} // ok

func main() {
    var f F[string]
    f.M1("hello")
}
Officially Maintained Generics Package

Go 1.18 provides a minimal version of Go generics, with a syntax and two predefined types: comparable and any. The constraints, slices, and maps generics package that was originally intended to be added to the standard library was put on hold due to a comment from Go’s old father, Rob Pike. Pike’s reasoning is simple: Go generics are the biggest language change since the birth of Go, and Go version 1.18 carries too many changes that are error-prone. And the Go core development team also had no experience with the new generics. He suggested that the Go core development team should wait, observe and learn more, and not take too big a step, and that Go should move steadily at its own pace.

So the three packages mentioned earlier were placed under golang.org/x/exp.

1
2
3
golang.org/x/exp/constraints
golang.org/x/exp/slices
golang.org/x/exp/maps

When the time is right, these packages will go into the Go standard library like the http2 packages did back in the day.

Go Toolchain Support for Generic Syntax

After the Go generic came out, the tools on the Go toolchain maintained by Go officials all basically settled on a plan to support generic syntax. By the time Go 1.18 was released, gofmt/goimports, go vet, and gopls (supported since v0.8.1) had all implemented support for generics.

But here, except for gofmt which is released with the Go installer, all other tools need to be installed and upgraded to the latest version by themselves. Otherwise your editor will give you all kinds of errors once you use the generic syntax or the new predefined identifiers like any, comparable, etc.

If you are using vim+vim-go+goimports+gopls like me, then for the editor to support go 1.18, you can use the following command to upgrade the tool version to support go 1.18 generics.

1
2
$go install golang.org/x/tools/cmd/goimports@latest
$go install golang.org/x/tools/gopls@latest

Of course there are many tools in the Go community that have not yet caught up with the pace in time, and this should give the Go community some time.

I will gradually explain the details and implementation principles of Go generic syntax in subsequent articles.

After talking about generics, let’s take a look at the changes in the Go compiler and Go module.

2. Go compiler and Go module changes

1. Fixed syntax bugs

We know that the Go compiler will report an error if a variable is not used after it is declared inside a Go function. But before Go 1.18, the Go compiler would not report an error for the variable p in the example below, even if it was not used in main.

Go 1.18 fixes this problem, and if you compile the example with Go 1.18, you will get the compiler error in the comment.

1
2
3
4
5
6
7
8
package main

func main() {
    p := true // go 1.18会报错:p declared but not used,但Go 1.18之前的版本不会。
    func() {
        p = true
    }()
}

Also, both gopls and go vet will give error messages for the above problem.

2. Introducing architectural level on AMD64 platform

It is well known that the Go language has a lot of room for improvement in terms of target code optimization. In Go 1.18, Go introduced an optimization measure of sorts, namely the concept of architectural level on the AMD64 platform. higher the level, the newer the available instructions, and the performance of the compiled code using the new instructions may be improved.

Go 1.18 uses the GOAMD64 environment variable to indicate the level used by the compiler, and by default uses version v1. This version uses all the instructions that are supported by x86-64 cpu’s in the production code. To be clear, it uses the most basic instructions, which are compatible, but also have the worst performance.

The other three candidates for the GOAMD64 environment variable are v2, v3, and v4. The higher the version, the worse the compatibility, but the performance may be improved by using new instructions.

  • GOAMD64=v2: all v1 commands, plus CMPXCHG16B, LAHF, SAHF, POPCNT, SSE3, SSE4.1, SSE4.2, SSSE3.
  • GOAMD64=v3: all v2 instructions, plus AVX, AVX2, BMI1, BMI2, F16C, FMA, LZCNT, MOVBE, OSXSAVE.
  • GOAMD64=v4: all v3 instructions, plus AVX512F, AVX512BW, AVX512CD, AVX512DQ, AVX512VL.

In the path of optimization, the Go team has been working hard, not that the Go compiler can now also inline functions with a range loop or a loop statement with a label.

3. Enriched SBOM information

Over the years, security issues about the software supply chain have been frequent, and the software supply chain has become a hot topic in the IT security field.

Go, as the head development language for cloud-native platforms, middleware and services, has become critical to its own security and the security of the software it builds. go is gradually increasing its investment in security, and the means are gradually increasing and enriching.

Go provides support for SBOM (Software Bill of Materials) as an important protection tool to mitigate software supply chain attacks in version 1.13, and in Go version 1.18, Go has enriched the SBOM information provided.

4. The negative impact of Go generics on the compiler

The introduction of Go generics has increased the expressiveness of the Go language, but it has also had a significant negative impact on the Go compiler, the biggest of which is compilation speed. From the Go 1.18 release notes documentation, Go 1.18 compiles 15% faster than Go 1.17, and this performance drop is there even if you don’t use the generic syntax in your code at all. So this is something the Go team is going to focus on in Go 1.19.

5. go module changes

As of Go 1.16, the Go module has reached maturity. However, there are still some minor issues that need to be fixed, one of which is which command has the right to modify go.mod and go.sum. Go 1.18 clarifies that there are only three commands that can modify go.mod and go.sum: go get, go mod tidy and go mod download. This allows developers to execute the This allows developers to execute the other commands provided by the go toolchain in the project root.

6. Introduction of Go workspace (workspace)

The introduction of Go module has greatly improved Go package dependency and build problems. However, there are still two problems that make Go module a poor experience in collaborative software development, and these two problems are difficult to be solved fundamentally under the original go module mechanism. These two problems are as follows.

  • making its own modifications to dependency packages and building based on locally modified dependency packages.
  • Rely on locally unpublished mods.

The original go module replace mechanism was a poor experience in collaborative situations and put some additional mental burden on developers. So Go developer Michael Matloob proposed a proposal called “Multi-Module Workspaces in cmd/go” in April 2021. This proposal introduces a go.work file to enable the Go workspace mode. go.work sets some local paths with the use indicator, and the go modules under these paths form a workspace, Go commands can manipulate the go modules under these paths, and will preferentially use the go modules in the workspace module . At the same time, go.work is local environment related and does not need to be committed to the code repository, each developer can have his own go.work file only according to his own development environment.

After looking at the compiler, let’s briefly talk about the rest of the tool chain.

3. Go toolchain changes

1. go fuzzing

The biggest change in the Go toolchain is the introduction of native support for fuzzing, also known as fuzz testing, or random testing. It is essentially an automated testing technique, or more specifically, an automated testing technique based on random input, and is often used to find bugs and problems in code that handles user input.

Instead of using a predefined data set as program input like unit testing, Fuzzing will construct some random data by the data construction engine itself or based on the initial data provided by the developer and provide it as input to our program, and then monitor the program for panic, assertion failure, infinite loop, etc. These constructed random data are called corpus. In addition Fuzz testing is not a one-time execution test, if there is no limit on the number of executions and execution time, Fuzz testing will keep on executing, so it is also a continuous testing technique.

Go 1.18 incorporates fuzz testing into the go test toolchain, making it an important member of the Go native testing toolchain along with unit testing and performance benchmarking.

The test cases for go fuzzing test are placed in xx_test.go like normal test cases (TestXxx) and performance benchmark tests (BenchmarkXxx), but the corresponding function name of the case is changed to FuzzXxx. A simple Fuzzing test example is as follows.

1
2
3
4
5
6
7
8
func FuzzXxx(f *testing.F) {
    // 设置种子语料(可选)

    // 执行Fuzzing
    f.Fuzz(func(t *testing.T, b []byte) {
        //... ...
    })
}

Here you need to pay extra attention to the fact that although Fuzzing tests are written very similar to unit tests and benchmark tests and are also very simple, Fuzzing tests are run continuously and do not stop, so as the Go 1.18 release notes suggest: Fuzzing tests consume a lot of memory and may affect your machine when running performance. Note also that at runtime, the fuzzing engine writes the expanded test range values to the fuzz cache directory in $GOCACHE/fuzz. There is currently no limit to the number of files or total bytes written to the fuzz cache, so it can take up a lot of storage space (possibly several GB or more). Therefore it is recommended to get a dedicated high end machine to run the fuzzing test.

2. go get

In go module build mode, go get returns to its job of fetching go modules and their corresponding dependencies, and no longer performs compilation and installation work. In this way, go install, which had been stripped of its halo by go get, regains its original function in module-aware mode: installing the specified or latest version of the module and executable.

Finally, let’s look at some other small changes.

4. Other minor changes

1. gofmt supports concurrency

“The gofmt code style is not someone’s favorite, it’s everyone’s favorite”. gofmt code style has become a consensus among Go developers and is integrated into the development culture of the Go language. go 1.18 brings concurrency support to Go developers with gofmt, and without a doubt, its biggest The biggest benefit of gofmt is that it is fast, especially on multi-core cpus. gofmt can use more computing power to quickly format the code style.

2. The built-in function append changes the expansion algorithm for slices

We all know that when append operates on slices, once the slice is full (len==cap), append will reallocate a larger underlying array and then copy the current slice elements into the new underlying array. Usually in the case of smaller size, append is expanded by 2x cap, and in the case of larger size, for example, it is already 1024, then Go 1.17 will not double allocation. the algorithm in Go 1.18 has some changes, the purpose is to make the changes around a threshold more silky smooth. Specific algorithm you see the following $GOROOT/src/runtime/slice.go in the growslice function in part of the logic.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
func growslice(et *_type, old slice, cap int) slice {
    ... ...

    newcap := old.cap
    doublecap := newcap + newcap
    if cap > doublecap {
        newcap = cap
    } else {
        const threshold = 256
        if old.cap < threshold {
            newcap = doublecap
        } else {
            // Check 0 < newcap to detect overflow
            // and prevent an infinite loop.
            for 0 < newcap && newcap < cap {
                // Transition from growing 2x for small slices
                // to growing 1.25x for large slices. This formula
                // gives a smooth-ish transition between the two.
                newcap += (newcap + 3*threshold) / 4
            }
            // Set newcap to the requested cap when
            // the newcap calculation overflowed.
            if newcap <= 0 {
                newcap = cap
            }
        }
    }
    ... ...
}

Also from the code, and Go 1.17 use 1024 as the size boundary different, Go 1.18 use 256 as the threshold. this we should pay attention to.

3. New net/netip package

The Go 1.18 standard library adds a new netip package under net. This comes from the original Go core developer Brad Fitzpatrick in his startup project tailscale problems. Brad found that the existing net.IP in the standard library, which represents IP-related information, was deficient in so many ways.

Brad Fitzpatrick

So Brad proposes to add a new representation of IP that takes up less memory, is immutable and comparable, and can be used as a map key, which is netip.Addr and a set of types and methods around netip.

There is a lot more to the netip package, so check out netip package ref to learn more about this package.

4. Two important security changes

Security is a growing concern, and the Go standard library is keeping pace with security trends.

In Go 1.18, the tls client will use TLS version 1.2 by default. Of course if you want to explicitly set Config.MinVersion to VersionTLS10, TLS 1.0 and 1.1 will still work.

In addition, the crypto/x509 package in Go 1.18 will by default reject certificates signed with SHA-1 hash functions (except for self-issued ones). SHA-1 can be temporarily supported via GODEBUG=x509sha1=1, but as of Go 1.19 SHA-1 will be permanently kicked out.

5. strings package and bytes package added Cut function

The strings and bytes packages both add the utility function Cut (note: the strings and bytes packages have a long tradition of API consistency). Take strings as an example, the semantics of the Cut function is to “cut” a certain segment of the input string.

The prototype of the Cut function is as follows.

1
func Cut(s, sep string) (before, after string, found bool)

If the part to be cut off is not found, the final return value is false, before is the original string s, and after is “”.

1
2
3
4
var s = "hello, golang"

b, a, f := strings.Cut(s, "java")
fmt.Printf("before=%s, after=%s, found=%t\n", b, a, f) // before=hello, golang, after=, found=false

If the part to be cut off is found, the last return value is true, before is the string before the “cut off part”, after is the string after the “cut off part”.

1
2
b, a, f = strings.Cut(s, "lang")
fmt.Printf("before=%s, after=%s, found=%t\n", b, a, f) // before=hello, go, after=, found=true

If there are multiple strings in the input string that match the part to be cut, the Cut function will only cut the first matching string.

1
2
b, a, f = strings.Cut(s, "o")
fmt.Printf("before=%s, after=%s, found=%t\n", b, a, f) // before=hell, after=, golang, found=true

6. runtime/pprof accuracy improvement

Go 1.18 runtime/pprof uses a per-thread timer to drive sampling on Linux, with the goal of improving the accuracy of sampled data under high load and reducing cases of lost or inaccurate data.

7. sync package adds Mutex.TryLock, RWMutex.TryLock and RWMutex.TryRLock

The Go team has added Mutex.TryLock, RWMutex.TryLock and RWMutex.TryRLock to the sync package despite strong requests from the community, but to be honest, I have not personally encountered a scenario where I have to use TryLock. Note that while proper uses of TryLock do exist, they are rare, and uses of TryLock are often a sign of a deeper problem with a mutex in a particular use.

Just try not to use it!

5. Summary

From the above, Go 1.18 is a really big change. Go 1.18 introduces generics, so I personally recommend holding off on using it in production environments. Just like go module was introduced and matured through go 1.11~go 1.16, the maturity of Go generic must take at least 2-3 versions. At this stage, we should first focus on learning about generics and how to use them to improve our code, but we should also pay attention to the fact that they increase the complexity of the code dramatically, and the code using them is bound to have a decrease in readability, so don’t abuse them, and don’t obviously use them like the C++ template. That runs counter to the design philosophy of the Go language.