Equality in Golang

By Michał Łowicki

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):

nan := math.NaN()pos_inf := math.Inf(1)neg_inf := math.Inf(-1)fmt.Println(nan == nan) // falsefmt.Println(pos_inf == pos_inf) // truefmt.Println(neg_inf == neg_inf) // true

fmt.Println(pos_inf == neg_inf) // false

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

var p1, p2 *stringname := "foo"fmt.Println(p1 == p2) // truep1 = &namep2 = &namefmt.Println(p1) // 0x40c148fmt.Println(p2) // 0x40c148fmt.Println(&p1) // 0x40c138fmt.Println(&p2) // 0x40c140fmt.Println(*p1) // foofmt.Println(*p2) // foo

fmt.Println(p1 == p2) // true

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):

type S struct{}
func main() { var p1, p2 *S s1 := S{} s2 := S{} p1 = &s1 p2 = &s2 fmt.Printf("%p\n", p1) // 0x1e52bc fmt.Printf("%p\n", p2) // 0x1e52bc fmt.Println(p1) // &{} fmt.Println(p2) // &{} fmt.Println(&p1) // 0x40c138 fmt.Println(&p2) // 0x40c140 fmt.Println(*p1) // {} fmt.Println(*p2) // {} fmt.Println(p1 == p2) // true

}

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.

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):

func f(ch1 chan int, ch2 *chan int) { fmt.Println(ch1 == *ch2) // true

}

func main() { var ch1, ch2 chan int fmt.Println(ch1 == ch2) // true ch1 = make(chan int) ch2 = make(chan int) fmt.Println(ch1 == ch2) // false ch2 = ch1 fmt.Printf("%p\n", &ch1) // 0x40c138 fmt.Printf("%p\n", &ch2) // 0x40c140 fmt.Println(ch1 == ch2) // true f(ch1, &ch1)

}

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):

type I interface{ m() }type T []bytefunc (t T) m() {}
func main() { var t T fmt.Println(t == nil) // true var i I = t fmt.Println(i == nil) // false fmt.Println(reflect.TypeOf(i)) // main.T fmt.Println(reflect.ValueOf(i).IsNil()) // true

}

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):

type A inttype B = A

type C int

type I interface{ m() }func (a A) m() {}func (c C) m() {}
func main() { var a I = A(1) var b I = B(1) var c I = C(1) fmt.Println(a == b) // true fmt.Println(b == c) // false fmt.Println(a == c) // false

}

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):

type I interface{ m() }type X intfunc (x X) m() {}type Y intfunc (y Y) m() {}type Z int
func main() { var i I = X(1) fmt.Println(i == X(1)) // true fmt.Println(i == Y(1)) // false // fmt.Println(i == Z(1)) // mismatched types I and C // fmt.Println(i == 1) // mismatched types I and int

}

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

type A []byte
func main() { var i interface{} = A{} var j interface{} = A{} fmt.Println(i == j)

}

Output:

panic: runtime error: comparing uncomparable type main.A

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

type A []byte
type B []byte
func main() { // A{} == A{} // slice can only be compared to nil var i interface{} = A{} var j interface{} = B{} fmt.Println(i == j) // false

}

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

type A struct { _ float64 f1 int F2 string

}

type B struct { _ float64 f1 int F2 string

}

func main() { fmt.Println(A{1.1, 2, "x"} == A{0.1, 2, "x"}) // true // fmt.Println(A{} == B{}) // mismatched types A and B

}

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.

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

type T struct { name string age int _ float64

}

func main() { x := [...]float64{1.1, 2, 3.14} fmt.Println(x == [...]float64{1.1, 2, 3.14}) // true y := [1]T{{"foo", 1, 0}} fmt.Println(y == [1]T{{"foo", 1, 1}}) // true

}

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

fmt.Println(strings.ToUpper("ł") == "Ł") // truefmt.Println("foo" == "foo") // truefmt.Println("foo" == "FOO") // falsefmt.Println("Michał" == "Michal") // falsefmt.Println("żondło" == "żondło") // truefmt.Println("żondło" != "żondło") // false

fmt.Println(strings.EqualFold("ąĆź", "ĄćŹ")) // true

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):

f := func(int) int { return 1 }g := func(int) int { return 2 }

f == g

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):

f1 := func(int) int { return 1 }m1 := make(map[int]int)s1 := make([]byte, 10)fmt.Println(f1 == nil) // falsefmt.Println(m1 == nil) // falsefmt.Println(s1 == nil) // falsevar f2 func()var m2 map[int]intvar s2 []bytefmt.Println(f2 == nil) // truefmt.Println(m2 == nil) // true

fmt.Println(s2 == nil) // true

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

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):

s1 := []byte{'f', 'o', 'o'}s2 := []byte{'f', 'o', 'o'}fmt.Println(bytes.Equal(s1, s2)) // trues2 = []byte{'b', 'a', 'r'}fmt.Println(bytes.Equal(s1, s2)) // falses2 = []byte{'f', 'O', 'O'}fmt.Println(bytes.EqualFold(s1, s2)) // trues1 = []byte("źdźbło")s2 = []byte("źdŹbŁO")fmt.Println(bytes.EqualFold(s1, s2)) // trues1 = []byte{}s2 = nil

fmt.Println(bytes.Equal(s1, s2)) // true

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.

This function is generic method to compare any values:

func DeepEqual(x, y interface{}) bool

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

m1 := map[string]int{"foo": 1, "bar": 2}m2 := map[string]int{"foo": 1, "bar": 2}// fmt.Println(m1 == m2) // map can only be compared to nilfmt.Println(reflect.DeepEqual(m1, m2)) // truem2 = map[string]int{"foo": 1, "bar": 3}fmt.Println(reflect.DeepEqual(m1, m2)) // falsem3 := map[string]interface{}{"foo": [2]int{1,2}}m4 := map[string]interface{}{"foo": [2]int{1,2}}fmt.Println(reflect.DeepEqual(m3, m4)) // truevar m5 map[float64]stringfmt.Println(reflect.DeepEqual(m5, nil)) // false

fmt.Println(m5 == nil) // true

and slices (source code):

s := []string{"foo"}fmt.Println(reflect.DeepEqual(s, []string{"foo"})) // truefmt.Println(reflect.DeepEqual(s, []string{"bar"})) // falses = nilfmt.Println(reflect.DeepEqual(s, []string{})) // falses = []string{}

fmt.Println(reflect.DeepEqual(s, []string{})) // true

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

type T struct { name string Age int

}

func main() { t := T{"foo", 10} fmt.Println(reflect.DeepEqual(t, T{"bar", 20})) // false fmt.Println(reflect.DeepEqual(t, T{"bar", 10})) // false fmt.Println(reflect.DeepEqual(t, T{"foo", 10})) // true

}

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

import (
"fmt"
"github.com/google/go-cmp/cmp"
)
type T struct { Name string Age int City string

}

func main() { x := T{"Michał", 99, "London"} y := T{"Adam", 88, "London"} if diff := cmp.Diff(x, y); diff != "" { fmt.Println(diff) }

}

Output:

main.T{- Name: "Michał",+ Name: "Adam",- Age: 99,+ Age: 88, City: "London",

}

Please check documentation out to learn more.

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

import ( "bytes" "crypto/subtle" "testing"

)

var ( x = []byte{'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a'} y = []byte{'b', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a'} z = []byte{'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'b'}

)

func BenchmarkBytesEqualXY(b *testing.B) { for n := 0; n < b.N; n++ { bytes.Equal(x, y) }

}

func BenchmarkBytesEqualXZ(b *testing.B) { for n := 0; n < b.N; n++ { bytes.Equal(x, z) }

}

func BenchmarkConstTimeCompXY(b *testing.B) { for n := 0; n < b.N; n++ { subtle.ConstantTimeCompare(x, y) }

}

func BenchmarkConstTimeCompXZ(b *testing.B) { for n := 0; n < b.N; n++ { subtle.ConstantTimeCompare(x, z) }

}

$ go test -bench=.goos: darwingoarch: amd64pkg: github.com/mlowicki/subtleBenchmarkBytesEqualXY-12 554047392 2.09 ns/opBenchmarkBytesEqualXZ-12 274583632 4.38 ns/opBenchmarkConstTimeCompXY-12 66717505 15.0 ns/op

BenchmarkConstTimeCompXZ-12 70632979 15.6 ns/op

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.