Cherry-Picked Features from Go 1.20

nalgeon

Anton Zhiyanov

Posted on February 7, 2023

Cherry-Picked Features from Go 1.20

Go 1.20 brought a lot of new features and improvements. In this post, I'd like to review the ones that caught my eye. This is by no means an exhaustive list; for that, see the official release notes.

These are the topics for review:

Each section has a playground link, so check those out.

Multi-errors

The "errors as values" concept (as opposed to exceptions) has gained renewed popularity in modern languages such as Go and Rust. You know this well because it's impossible to take a step without tripping over an error value in Go.

Go 1.20 has brought us new joy — the combination of errors through errors.Join():

errRaining := errors.New("it's raining")
errWindy := errors.New("it's windy")
err := errors.Join(errRaining, errWindy)
Enter fullscreen mode Exit fullscreen mode

Now err is both errRaining and errWindy at the same time. The standard functions errors.Is() and errors.As() can work with this:

if errors.Is(err, errRaining) {
    fmt.Println("ouch!")
}
// ouch!
Enter fullscreen mode Exit fullscreen mode

fmt.Errorf() has also learned to combine errors:

err := fmt.Errorf(
    "reasons to skip work: %w, %w",
    errRaining,
    errWindy,
)
Enter fullscreen mode Exit fullscreen mode

To accept multiple errors in your own error type, return []error from the Unwrap() method:

type RefusalErr struct {
    reasons []error
}

func (e RefusalErr) Unwrap() []error {
    return e.reasons
}

func (e RefusalErr) Error() string {
    return fmt.Sprintf("refusing: %v", e.reasons)
}
Enter fullscreen mode Exit fullscreen mode

If you love errors, this change will definitely be to your liking. If not... well, you always have panic :)

playground

'Context Canceled' Cause

A context.Canceled error occurs when the context is canceled. This is no news:

ctx, cancel := context.WithCancel(context.Background())
cancel()

fmt.Println(ctx.Err())
// context canceled
Enter fullscreen mode Exit fullscreen mode

Starting from 1.20, we can create a context using context.WithCancelCause(). Then cancel() will take one parameter — the root cause of the error:

ctx, cancel := context.WithCancelCause(context.Background())
cancel(errors.New("the night is dark"))
Enter fullscreen mode Exit fullscreen mode

context.Cause() extracts the cause of the error from the context:

fmt.Println(ctx.Err())
// context canceled

fmt.Println(context.Cause(ctx))
// the night is dark
Enter fullscreen mode Exit fullscreen mode

You may ask — why context.Cause()? It seems logical to add the Cause() method to the context itself, similar to the Err() method.

Sure. But Context is an interface. And any change to the interface breaks backward compatibility. That's why it was done differently.

playground

New Date Formats

Please don't be offended by this section if you are a North American. We love you people, but sometimes your view of the world can be a bit... biased.

You surely know that the Go authors chose a quite unorthodox format for the date and time layout.

For example, parsing the date 2023-01-25 09:30 looks like this:

const layout = "2006-01-02 15:04"
t, _ := time.Parse(layout, "2023-01-25 09:30")
fmt.Println(t)
// 2023-01-25 09:30:00 +0000 UTC
Enter fullscreen mode Exit fullscreen mode

While 01/02 03:04:05PM '06 may be a nice mnemonic in the US, it's entirely cryptic for the European (or Asian) eye.

The Go authors have thoughtfully provided 12 standard date/time masks, of which only RFC3339 and RFC3339Nano are suitable for non-Americans. Others are as mysterious as the imperial measurement system:

Layout      = "01/02 03:04:05PM '06 -0700"
ANSIC       = "Mon Jan _2 15:04:05 2006"
UnixDate    = "Mon Jan _2 15:04:05 MST 2006"
RubyDate    = "Mon Jan 02 15:04:05 -0700 2006"
RFC822      = "02 Jan 06 15:04 MST"
RFC822Z     = "02 Jan 06 15:04 -0700"
RFC850      = "Monday, 02-Jan-06 15:04:05 MST"
RFC1123     = "Mon, 02 Jan 2006 15:04:05 MST"
RFC1123Z    = "Mon, 02 Jan 2006 15:04:05 -0700"
Kitchen     = "3:04PM"
Enter fullscreen mode Exit fullscreen mode

Ten years have passed, and the Go development team began to suspect something. They learned that there are several other popular date formats worldwide. And, starting with version 1.20, they added three new masks:

DateTime = "2006-01-02 15:04:05"
DateOnly = "2006-01-02"
TimeOnly = "15:04:05"
Enter fullscreen mode Exit fullscreen mode

Now we can finally do this:

t, _ := time.Parse(time.DateOnly, "2023-01-25")
fmt.Println(t)
// 2023-01-25 00:00:00 +0000 UTC
Enter fullscreen mode Exit fullscreen mode

Nice!

playground

Slice to Array Conversion

Starting from version 1.17, we can get a pointer to an array under the slice:

s := []int{1, 2, 3}
arrp := (*[3]int)(s)
Enter fullscreen mode Exit fullscreen mode

By changing the array through the pointer, we are also changing the slice:

arrp[2] = 42
fmt.Println(s)
// [1 2 42]
Enter fullscreen mode Exit fullscreen mode

In Go 1.20, we can also get a copy of the array under the slice:

s := []int{1, 2, 3}
arr := [3]int(s)
Enter fullscreen mode Exit fullscreen mode

Changes in such an array are not reflected in the slice:

arr[2] = 42
fmt.Println(arr)
// [1 2 42]
fmt.Println(s)
// [1 2 3]
Enter fullscreen mode Exit fullscreen mode

This is, in essence, syntactic sugar because we could get a copy of the array before:

s := []int{1, 2, 3}
arr := *(*[3]int)(s)
Enter fullscreen mode Exit fullscreen mode

The new notation is cleaner, of course.

playground

Other Notable Changes

bytes.Clone() function clones a byte slice:

b := []byte("abc")
clone := bytes.Clone(b)
Enter fullscreen mode Exit fullscreen mode

math/rand package now automatically initializes the random number generator with a random starting value, so there is no need for a separate rand.Seed() call.

strings.CutPrefix() and strings.CutSuffix() functions trim a prefix/suffix from a string similarly to TrimPrefix/TrimSuffix, but they also indicate whether the prefix was present in the string:

s := "> go!"
s, found := strings.CutPrefix(s, "> ")
fmt.Println(s, found)
// go! true
Enter fullscreen mode Exit fullscreen mode

sync.Map now has atomic methods Swap, CompareAndSwap, and CompareAndDelete:

var m sync.Map
m.Store("name", "Alice")
prev, ok := m.Swap("name", "Bob")
fmt.Println(prev, ok)
// Alice true
Enter fullscreen mode Exit fullscreen mode

time.Compare() function compares two times and returns -1/0/1 based on the comparison results:

t1 := time.Now()
t2 := t1.Add(10 * time.Minute)
cmp := t2.Compare(t1)
fmt.Println(cmp)
// 1
Enter fullscreen mode Exit fullscreen mode

Overall, a great release! Looking forward to trying everything in production.

Follow @ohmypy on Twitter to keep up with new posts

💖 💪 🙅 🚩
nalgeon
Anton Zhiyanov

Posted on February 7, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related