Go 1.20 was released on February 1, 2023. That means it’s time for the final part of this three part look at What’s New in Go 1.20. In this part, we’ll look at some of the relatively minor changes to the standard library.

Before we begin, here are two changes I won’t write about because someone else did. The first is the new context.WithCancelCause API. Joseph Woodward wrote a great summary of the issue, so please read that.

Second, Filippo Valsorda wrote a complete look at Go 1.20 cryptography changes, including the new crypto/ecdh package. So, check that out for more details on those changes, including the secret crypto/internal/bigmod package 🤫.


Speaking of cryptography, I will briefly call out one cryptography-related change. The crypto/subtle package gains a XORBytes function. If you’ve ever tried the Cryptopals challenges, it’s literally the second challenge, before any of the actually hard crypto stuff starts. It’s nice to have a built-in version in the standard library now.


In Go 1.20, the math/random package has one change and also one crypto-adjacent non-change.

The Go team have been aware of flaws in the math/rand package for years. All the way back in 2017, Rob Pike wrote:

Changing math/rand to use [a different algorithm] is strictly incompatible, but not in a way that the Go 1 decree actually prohibits. A soft breakage at this level would be an excellent step on the way to understanding what it takes to roll out a breaking change for Go 2.

After this was written, it was decided that “Go 2” would be backwards compatible to the furthest extent possible, but that still leaves the Go team with the question of what to do with packages like math/rand that probably really do need breaking changes.

One change made in Go 1.20 is that math/rand is now random by default. Before it always began by using a set sequence until you called RANDOMIZE TIMER rand.Seed. Seeding math/rand by default is sort of a breaking change, but in a weird kind of way. It only breaks code that a) depends on having a consistent sequence from math/rand, but b) does not use its own *rand.Rand to specify a source with a known initial seed.

On the one hand, it’s certain that this will break someone’s code out there. On the other hand, it’s hard to say that they weren’t warned. The package is named “rand”, so one might reasonably expect random behavior from it. If this new behavior breaks your code and you cannot rewrite it to work correctly, you can use GODEBUG=randautoseed=0 to disable it for now.

The non-change to the math/rand package is now rand.Read is officially deprecated. (Note that because of the Go 1 guarantee, deprecated functions aren’t ever actually removed from the standard library. They just produce warnings in various tools.) There basically was never a good reason to use rand.Read. It fills a buffer with some random bits, but if you’re doing that, you’re probably doing crypto, in which case, you should use crypto/rand.Read instead. The sequences produced by math/rand are only pseudorandom. A dedicated hacker can possibly reverse them, so it must never be used for cryptography. The formal deprecation of math/rand.Read doesn’t actually change anything, but it does emphasize publicly something that people with knowledge of the Go standard library have already known for years.


The path/filepath package got a handy new function called IsLocal. “Localness” is a property that didn’t have a good name until now. If you have a path that begins with a “/”, that’s an “absolute” path. If you have a path like “./file” or “../dir”, that’s a “relative” path. But what do you call the idea that once all the relative directories are evaluated, the resulting directory path does not go up into a parent directory, like “nothingtosee/../../../../etc/passwd”? Well, now that concept has a name, and it’s a called “IsLocal”.

filepath.IsLocal also tests for special names on Windows, like NUL. It’s particularly handy if you want to use a path with an fs.FS, since fs.FS expects all paths to be local.


In Go 1.20, users of httputil.ReverseProxy will now have a little more control of exactly what fields are forwarded to the destination server.

Traditionally, advanced users of the httputil.ReverseProxy used the Director field to modify a single request before it was forwarded. This was a little bit clunky and led to some security issues with fields being accidentally forwarded when they should or shouldn’t be. The new Rewrite field separates the incoming request from the outgoing request and adds a few helper methods to make it easier to write a secure forwarding proxy.


Finally, if you have ever written this code:

if strings.HasPrefix(s, "something/") {
    s = strings.TrimPrefix(s, "something/")
    // do something with short s
} else {
   // do something else with long s
}

You can now use strings.CutPrefix to type slightly less:

s, ok := strings.CutPrefix(s, "something/")
if ok {
    // do something with short s
} else {
   // do something else with long s
}

There is also a strings.CutSuffix, and there are new bytes versions of both.


These are just a handful of the many minor additions in Go 1.20. There is still a lot more for someone to blog about like Profiler Guided Optimization or runtime coverage testing and the usual compile time and garbage collection improvements, but whatever your favorite features are, all around it’s a very solid release with a lot of substantive improvements.