Equality in Golang

Tale of comparison operators and {DeepEqual,Equal,EqualFold} methods

Michał Łowicki
Feb 26 · 8 min read

You’ve probably seen == or != operators many times in .go files. It turns out that some types like e.g. maps or slices can’t be used as operands to these operators. It’s sometimes needed to compare such values though. In this post we’ll explore all rules behind equality operators in Go, how it plays with Unicode and what are the methods to compare non-comparable types.


Let’s start with something simple and actually not hiding many secrets. For booleans and numeric types (floats, integers, complex numbers) equality operators work without bigger surprises. Type float64 has few special values like NaN (IEEE 754 “not a number” value), positive and negative infinity. It’s worth to mention that NaN is not equal to NaN (source code):

Pointers

Two pointers are equal if either both are nil or both point to exactly the same address in memory (source code):

Language spec also says:

A struct or array type has size zero if it contains no fields (or elements, respectively) that have a size greater than zero. Two distinct zero-size variables may have the same address in memory.

Let’s see that rule in action (source code):

By changing S type definition to non-empty struct like S struct {f int} you’ll see that p1 and p2 are not equal anymore — source code.

Channels

With basic concurrency primitive in Go we’ve two rules i.e. two channels are equal when either:

  • both are nil
  • both are created by the same call to built-in function make

Code snippet above demonstrates this behaviour (source code):

Interfaces

First case might seem simple — two interface values are equal if both are nil. It’s important to remember when exactly interface value is nil. It happens when both dynamic type and dynamic value are nil (source code):

More about interfaces in earlier series — https://medium.com/golangspec/interfaces-in-go-part-i-4ae53a97479c.

If dynamic types are identical and dynamic values are equal then two interface values are equal (source code):

It’s possible to compare value x of non-interface type X with value i of interface type I . There are few limitations though:

  • type X implements interface I
  • type X is comparable

If dynamic type of i is X and dynamic value of i is equal to x then values are equal (source code):

If dynamic types of interface values are identical but not comparable then it will generate runtime panic (source code):

Output:

If types are different but still not comparable then interface values aren’t equal (source code):

Structs

While comparing structs, corresponding non-blank fields are checked for equality — both exported and non-exported (source code):

It’s worth to introduce now main rule applicable not only for structs but all types:

x == y is allowed only when either x is assignable to y or y is assignable to x.

This is why A{} == B{} above generates compile-time error.

Arrays

This is similar to struct explained earlier. Corresponding elements needs to be equal for the whole arrays to be equal (source code):

Strings

Strings are in effect immutable slices of bytes and equality works for them by running comparison byte by byte (source code):

Comparing something non-comparable

https://github.com/egonelbre

In this bucket we’ve three types: functions, maps and slices. We can’t do much about the functions. There is not way to compare them in Go (source code):

It generates compile-time error: invalid operation: f == g (func can only be compared to nil). It also gives a hint that we can compare functions to nil. The same is true for maps and slices (source code):

Are there any options for maps or slices though? Luckily there are and we’ll explore them right now…

[]byte

Package bytes offers utilities to deal with byte slices and it provides functions to check if slices are equal and even equal under Unicode case-folding (source code):

What about maps or slices where elements of underlying arrays are not bytes? We’ve two options: reflect.DeepEqual , cmp package or writing ad-hoc comparison code using e.g. for statement. Let’s see first two approaches in action.

reflect.DeepEqual

This function is generic method to compare any values:

Let’s see how it works with maps (source code):

and slices (source code):

You can even apply it to types covered earlier like structs (source code):

cmp package

Package cmp offers additional capabilities like customisable Equal methods , option to ignore non-exported struct fields or get diff of two values (source code):

Output:

Please check documentation out to learn more.

Timing attacks

To prevent timing attacks there’s a standard package with function where time to compare slices is independent of the parameters’ content.

Time to compare x and y using bytes.Equal is doubled compared to x and z so it clearly depends on the content of parameters as length is always the same in tests above. That difference is negligible when using subtle.ConstantTimeCompare.


👏👏👏 below to help others discover this story. Please follow me here or on Twitter if you want to get updates about new posts or boost work on future stories.

Resources

golangspec

A series dedicated to deeply understand Go’s specification…

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store