Go 1.22 has been released for a couple of months as of this writing. It’s long past time to wrap up my series on what I worked on for 1.22. Sorry for the long delay, I’ve been busy with life stuff. Be sure to catch up on my posts about reflect.TypeFor and slices.Concat if you missed those.

The final function I proposed and implemented for Go 1.22 is cmp.Or. On Go Time, I called it “the hidden gem of 1.22”. It’s a simple function with a lot of potential uses and a surprisingly long backstory.

First let’s take a look at the code:

// Or returns the first of its arguments that is not equal to the zero value.
// If no argument is non-zero, it returns the zero value.
func Or[T comparable](vals ...T) T {
	var zero T
	for _, val := range vals {
		if val != zero {
			return val
		}
	}
	return zero
}

As is usual for my contributions to Go, it’s very short and simple. It just compares its arguments and returns the first one that isn’t 0 or nil or "" or whatever the zero value is for its type.


How do you use it?

 

The primary use for cmp.Or is for taking strings and returning the first one that isn’t blank. For example, searching public open source Go repositories, I found a lot of code online that tries to fetch an environmental variable but return a default value if it’s blank. With cmp.Or, this would look like cmp.Or(os.Getenv("SOME_VARIABLE"), "default").

It also works with numbers and pointers.

Here are a handful of actual uses from a real codebase of mine:

body := cmp.Or(page.Body, rawContent)
name := cmp.Or(jwt.Username(), "Almanack")
credits = append(credits, cmp.Or(credit.Name, credit.Byline))
metadata.InternalID = cmp.Or(
    xhtml.InnerText(rows.Value("slug")),
    xhtml.InnerText(rows.Value("internal id")),
    metadata.InternalID,
)
scope.SetTag("username", cmp.Or(userinfo.Username(), "anonymous"))
currentUl = cmp.Or(
    xhtml.Closest(currentUl.Parent, xhtml.WithAtom(atom.Ul)),
    currentUl,
)

As you can see, most uses just look at strings to provide a fallback value, but the final example is looking for a non-nil *html.Node.

The other major use for cmp.Or is to use with cmp.Compare to create multipart comparisons:

type Order struct {
	Product  string
	Customer string
	Price    float64
}
orders := []Order{
	{"foo", "alice", 1.00},
	{"bar", "bob", 3.00},
	{"baz", "carol", 4.00},
	{"foo", "alice", 2.00},
	{"bar", "carol", 1.00},
	{"foo", "bob", 4.00},
}
// Sort by customer first, product second, and last by higher price
slices.SortFunc(orders, func(a, b Order) int {
	return cmp.Or(
		cmp.Compare(a.Customer, b.Customer),
		cmp.Compare(a.Product, b.Product),
		cmp.Compare(b.Price, a.Price),
	)
})
foo alice 2.00
foo alice 1.00
bar bob 3.00
foo bob 4.00
bar carol 1.00
baz carol 4.00

Note that because cmp.Or cannot do short-circuit evaluation, this will compare the product name and price of each item, even if the customer name is different, which makes doing so redundant.


The road that led to cmp.Or is long. All the way back in 2016, Stephen Kampmann proposed strings.First, but this was before the modern Go proposal system, so the proposal wasn’t actually evaluated. In 2020, I proposed a new operator, ??, that would work like cmp.Or but with short circuiting. It could be used like port := os.Getenv("PORT") ?? DefaultPort. Ian Lance Taylor noted at the time that if Go ever got generics, this could be implemented (without short-circuiting) as a generic helper function. Shortly after opening the issue for ??, I ended up writing a stringutils.First helper function for my own personal use, and I soon found myself using it everywhere in my code.

When generics were finally added to beta versions of Go in 2021, I wrote a package called truthy that uses reflect.Value.IsZero() to report whether any value is zero, but I found that using reflection caused allocations (this was eventually improved) and was 50x slower than a normal comparison (this has improved to only being 25x in Go 1.22). Using just generics with comparable like cmp.Or and not reflection has about a 2x penalty in the current version of Go.

In 2022, I made a proposal for a generic universal zero value that was closed as a duplicate of an existing discussion, but Russ Cox made essentially the same proposal in 2023 and it was accepted. According to the proposal, the built in constant zero would be available in generic functions for return or comparison. However, after the proposal was accepted, it was retracted due to continued community push back against the idea of having both nil and zero in the language. I still think this is a shame and hope that someday cmp.Or can be made fully generic and work for any type, but for now, it only works for comparable types.

The crosseyed *new(T) lives on…

Initially, I proposed just to add strings.First to the standard library, but in the course of the discussion, several commenters preferred a generic version of the function and Russ Cox proposed the name cmp.Or, which was ultimately accepted.

So that wraps up my work on Go 1.22! Come back this summer for the story of reflect.Value.Seq().