leiyonglin
leiyonglin

Reputation: 6814

How to compare if two structs, slices or maps are equal?

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

Upvotes: 227

Views: 286806

Answers (7)

Ilia Choly
Ilia Choly

Reputation: 18557

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

Update: Go 1.21

import (
    "maps"
    "slices"
}

func (t *T) Equal(t2 *T) bool {
    if t == t2 {
        return true
    }
    return t.X == t2.T &&
        t.Y == t2.Y &&
        slices.Equal(t.Z, t2.Z) &&
        maps.Equal(t.M, t2.M)
}

Upvotes: 35

blackgreen
blackgreen

Reputation: 44698

Go 1.21

Same as below, but maps.Equal is now in the standard library, you can just use that instead of importing experimental packages or rolling your own.

Go 1.18 ~ 1.20

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

Upvotes: 7

Cole Bittel
Cole Bittel

Reputation: 2884

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.

Upvotes: 142

wst
wst

Reputation: 4338

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
}

Upvotes: 19

Riyaz Khan
Riyaz Khan

Reputation: 3238

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: "[email protected]",
    }
    u2 := User{
            name: "Iron Man", 
            email: "[email protected]",
    }
        // 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.

Upvotes: 2

赣西狠人
赣西狠人

Reputation: 342

If you're comparing them in unit test, a handy alternative is EqualValues function in testify.

Upvotes: 11

OneOfOne
OneOfOne

Reputation: 99234

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

Upvotes: 231

Related Questions