golangspec
Published in

golangspec

Equality in Golang

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

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

Pointers

var p1, p2 *string
name := "foo"
fmt.Println(p1 == p2) // true
p1 = &name
p2 = &name
fmt.Println(p1) // 0x40c148
fmt.Println(p2) // 0x40c148
fmt.Println(&p1) // 0x40c138
fmt.Println(&p2) // 0x40c140
fmt.Println(*p1) // foo
fmt.Println(*p2) // foo
fmt.Println(p1 == p2) // true
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
}

Channels

  • both are nil
  • both are created by the same call to built-in function make
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)
}

Interfaces

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
}
type A int
type 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
}
  • type X implements interface I
  • type X is comparable
type I interface{ m() }type X intfunc (x X) m() {}type Y intfunc (y Y) m() {}type Z intfunc 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
}
type A []bytefunc main() {
var i interface{} = A{}
var j interface{} = A{}
fmt.Println(i == j)
}
panic: runtime error: comparing uncomparable type main.A
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
}

Structs

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
}

Arrays

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

fmt.Println(strings.ToUpper("ł") == "Ł")     // true
fmt.Println("foo" == "foo") // true
fmt.Println("foo" == "FOO") // false
fmt.Println("Michał" == "Michal") // false
fmt.Println("żondło" == "żondło") // true
fmt.Println("żondło" != "żondło") // false
fmt.Println(strings.EqualFold("ąĆź", "ĄćŹ")) // true

Comparing something non-comparable

https://github.com/egonelbre
f := func(int) int { return 1 }
g := func(int) int { return 2 }
f == g
f1 := func(int) int { return 1 }
m1 := make(map[int]int)
s1 := make([]byte, 10)
fmt.Println(f1 == nil) // false
fmt.Println(m1 == nil) // false
fmt.Println(s1 == nil) // false
var f2 func()
var m2 map[int]int
var s2 []byte
fmt.Println(f2 == nil) // true
fmt.Println(m2 == nil) // true
fmt.Println(s2 == nil) // true

[]byte

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

reflect.DeepEqual

func DeepEqual(x, y interface{}) bool
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 nil
fmt.Println(reflect.DeepEqual(m1, m2)) // true
m2 = map[string]int{"foo": 1, "bar": 3}
fmt.Println(reflect.DeepEqual(m1, m2)) // false
m3 := map[string]interface{}{"foo": [2]int{1,2}}
m4 := map[string]interface{}{"foo": [2]int{1,2}}
fmt.Println(reflect.DeepEqual(m3, m4)) // true
var m5 map[float64]string
fmt.Println(reflect.DeepEqual(m5, nil)) // false
fmt.Println(m5 == nil) // true
s := []string{"foo"}
fmt.Println(reflect.DeepEqual(s, []string{"foo"})) // true
fmt.Println(reflect.DeepEqual(s, []string{"bar"})) // false
s = nil
fmt.Println(reflect.DeepEqual(s, []string{})) // false
s = []string{}
fmt.Println(reflect.DeepEqual(s, []string{})) // true
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
}

cmp package

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)
}
}
  main.T{
- Name: "Michał",
+ Name: "Adam",
- Age: 99,
+ Age: 88,
City: "London",
}

Timing attacks

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: darwin
goarch: amd64
pkg: github.com/mlowicki/subtle
BenchmarkBytesEqualXY-12 554047392 2.09 ns/op
BenchmarkBytesEqualXZ-12 274583632 4.38 ns/op
BenchmarkConstTimeCompXY-12 66717505 15.0 ns/op
BenchmarkConstTimeCompXZ-12 70632979 15.6 ns/op

Resources

--

--

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
Michał Łowicki

Software engineer at Datadog, previously at Facebook and Opera, never satisfied.