I want to check if two structs, slices and maps are equal.
But I'm running into problems with the following code. See my comments at the relevant lines.
package main
import (
"fmt"
"reflect"
)
type T struct {
X int
Y string
Z []int
M map[string]int
}
func main() {
t1 := T{
X: 1,
Y: "lei",
Z: []int{1, 2, 3},
M: map[string]int{
"a": 1,
"b": 2,
},
}
t2 := T{
X: 1,
Y: "lei",
Z: []int{1, 2, 3},
M: map[string]int{
"a": 1,
"b": 2,
},
}
fmt.Println(t2 == t1)
//error - invalid operation: t2 == t1 (struct containing []int cannot be compared)
fmt.Println(reflect.ValueOf(t2) == reflect.ValueOf(t1))
//false
fmt.Println(reflect.TypeOf(t2) == reflect.TypeOf(t1))
//true
//Update: slice or map
a1 := []int{1, 2, 3, 4}
a2 := []int{1, 2, 3, 4}
fmt.Println(a1 == a2)
//invalid operation: a1 == a2 (slice can only be compared to nil)
m1 := map[string]int{
"a": 1,
"b": 2,
}
m2 := map[string]int{
"a": 1,
"b": 2,
}
fmt.Println(m1 == m2)
// m1 == m2 (map can only be compared to nil)
}
http://play.golang.org/p/AZIzW2WunI
You can use reflect.DeepEqual, or you can implement your own function (which performance wise would be better than using reflection):
http://play.golang.org/p/CPdfsYGNy_
m1 := map[string]int{
"a":1,
"b":2,
}
m2 := map[string]int{
"a":1,
"b":2,
}
fmt.Println(reflect.DeepEqual(m1, m2))
reflect.DeepEqual
is often incorrectly used to compare two like structs, as in your question.
cmp.Equal
is a better tool for comparing structs.
To see why reflection is ill-advised, let's look at the documentation:
Struct values are deeply equal if their corresponding fields, both exported and unexported, are deeply equal. .... numbers, bools, strings, and channels - are deeply equal if they are equal using Go's == operator.
If we compare two time.Time
values of the same UTC time, t1 == t2
will be false if their metadata timezone is different.
go-cmp
looks for the Equal()
method and uses that to correctly compare times.
Example:
m1 := map[string]int{
"a": 1,
"b": 2,
}
m2 := map[string]int{
"a": 1,
"b": 2,
}
fmt.Println(cmp.Equal(m1, m2)) // will result in true
Important Note:
Be careful when using cmp.Equal
as it may lead to a panic condition
It is intended to only be used in tests, as performance is not a goal and it may panic if it cannot compare the values. Its propensity towards panicking means that its unsuitable for production environments where a spurious panic may be fatal.
go-cmp
and not reflect
.
Here's how you'd roll your own function http://play.golang.org/p/Qgw7XuLNhb
func compare(a, b *T) bool {
if a == b {
return true
}
if a.X != b.X || a.Y != b.Y {
return false
}
if len(a.Z) != len(b.Z) || len(a.M) != len(b.M) {
return false
}
for i, v := range a.Z {
if b.Z[i] != v {
return false
}
}
for k, v := range a.M {
if b.M[k] != v {
return false
}
}
return true
}
Update: Go 1.18
import (
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
)
func compare(a, b *T) bool {
if a == b {
return true
}
if a.X != b.X {
return false
}
if a.Y != b.Y {
return false
}
if !slices.Equal(a.Z, b.Z) {
return false
}
return maps.Equal(a.M, b.M)
}
if len(a.Z) != len(b.Z) || len(a.M) != len(b.M) { return false }
, because one of them could have extra fields.
if a == b ...
which doesn't compile (hence the question from OP). I suppose you wanted to follow up on Sean's note. You'd have to change the T
into *T
then that first if()
would compile (and work as expected).
If you intend to use it in tests, since July 2017 you can use cmp.Equal
with cmpopts.IgnoreFields
option.
func TestPerson(t *testing.T) {
type person struct {
ID int
Name string
}
p1 := person{ID: 1, Name: "john doe"}
p2 := person{ID: 2, Name: "john doe"}
println(cmp.Equal(p1, p2))
println(cmp.Equal(p1, p2, cmpopts.IgnoreFields(person{}, "ID")))
// Prints:
// false
// true
}
If you want to compare simple one-level structs, the best and simple method is the if
statement.
Like this if s1 == s2
Here is a simple example:
type User struct {
name string
email string
}
func main() {
u1 := User{
name: "Iron Man",
email: "ironman@avengers.com",
}
u2 := User{
name: "Iron Man",
email: "ironman@avengers.com",
}
// Comparing 2 structs
if u1 == u2 {
fmt.Println("u1 is equal to u2")
} else {
fmt.Println("u1 is not equal to u2")
}
}
Result: u1 is equal to u2
You can play with this here.
New way to compare maps
This proposal (https://github.com/golang/go/issues/47649) that is part of the future implementation of Go generics introduces a new function to compare two maps, maps.Equal
:
// Equal reports whether two maps contain the same key/value pairs.
// Values are compared using ==.
func Equal[M1, M2 constraints.Map[K, V], K, V comparable](m1 M1, m2 M2) bool
Example use
strMapX := map[string]int{
"one": 1,
"two": 2,
}
strMapY := map[string]int{
"one": 1,
"two": 2,
}
equal := maps.Equal(strMapX, strMapY)
// equal is true
maps
package is found in golang.org/x/exp/maps
. This is experimental and outside of Go compatibility guarantee. They aim to move it into the std lib in Go 1.19
You can see it working in gotip playground https://gotipplay.golang.org/p/M0T6bCm1_3m
Success story sharing
cmp.Equal(p1, p2, cmpopts.IgnoreFields(person{}, "ID"))
?