Comparing Values in Go

Once again, I started my Sunday thinking about an introductory topic to help out those learning Go (or refresher for those already doing Go). This time my write up is about comparing, ordering, and sorting values in Go.

If you come from an OO language, as I did, you are likely familiar with the fact that comparing object instances is handled by either some form of operator overloading or by overriding an equal() or compareTo() method (or similar), attach to your object, to define how an instance should compare itself to another.

In Go, there is a small set of known types (even user-defined types are based on them). Therefore, Go’s rules to comparing values is baked directly in the language. In general, two values can only be compared if one value is of the same (or underlying) type with the other. There are, however, more nuances to this rule that are explored in the next section.

Comparing Values

Equality is tested using the == and the != comparison operators. Most types can be tested for equality, some have limited support while others, however, not at all. The ordering operators <, <=, >, and >= are used to test values with types that can be ordered. Some types can be ordered while others not all. Let us look at how to compare each type in Go.

Boolean

Booleans can be compared to pre-defined values of (or expressions that produce) true or false. It is an error to attempt to compare a boolean value to an numeric value.

var a := true
if a != (10 == 20) {
fmt.Println("a not true")
}
// following blows up at compilation
if a == 1 { ... }

Integer and Floating-point numbers

Comparing numerical values works as you would expect, following the general rule above, for both equality and order.

import (
"fmt"
"math"
)
func main() {
a := 3.1415
if a != math.Pi {
fmt.Println("a is not pi")
}
}

Due to Go’s strict type adherence however, integers can only be compared with other integers and floating point values can only be compared with other floating point values (or expression that produce them). If you attempt to cross the integer to floating-point boundary, by comparing values of different types, the type system will require a conversion or risk failing at compile time.

func main() {
a := 3.1415
b := 6
if a != b {
fmt.Println("a is not b")
}else if a <= b {
fmt.Println("a is in the right position")
}
}
// Boom, blows up with: 
// operation: a != b (mismatched types float64 and int)

In the previous example, this code will only compile if both variables are declared with the same types or converted to be of the same type.

func main() {
a := 3.1415
b := 6
if a != float64(b) {
fmt.Println("a is not b")
}
}

Complex Numbers

Complex numbers can also be tested for equality. Two complex values are equal if their real and imaginary parts are equal respectively.

func main() {
a := complex(-3.25, 2)
b := -3.25 + 2i
if a == b {
fmt.Println("a complex as b")
}
}

Due to the nature of complex numbers, however, they do not support ordering operators in Go.

func main() {
a := complex(-3.25, 2)
b := -3.25 + 2i
if a < b {
fmt.Println("a complex as b")
}
}
//compilation error
//invalid operation: a <= b (operator <= not defined on complex128)

String Values

String values support both the standard equality and ordering operators. There are no additional functions needed to compare strings. Values can be automatically compared lexically using ==!=, <=, <, >, and >= operators.

func main() {
cols := []string{
"xanadu", "red", "fulvous",
"white", "green", "blue",
"orange", "black", "almond"}
for _, col := range cols {
if col >= "red" || col == "black" {
fmt.Println(col)
}
}
}

Struct Values

Two struct values can be tested for equality by comparing the values of their individual fields. In general, two struct values are considered equal if they are of the same type and the their corresponding fields are equal.

func main() {
p1 := struct {a string; b int}{"left", 4}
p2 := struct {a string; b int}{a: "left", b: 4}
if p1 == p2 {
fmt.Println("Same position")
}
}

In the previous code snippet, struct p1 is equal to p2 since they are of the same type and their corresponding field values are the same. Any change in the field values will cause the structs to be not equal.

Struct values, however, cannot be compared using ordering operators. So the following code will not compile.

func main() {
p1 := struct {a string; b int}{"left", 4}
p2 := struct {a string; b int32}{a: "left", b: 4}
if p1 > p2 {
fmt.Println("Same position")
}
}
// compilation error
// invalid operation: p1 > p2 (operator > not defined on struct)

Arrays

Array values are compared for equality by comparing elements of the their defined types. Arrays are equal if their corresponding values are equal.

func main() {
pair1 := [2]int {4, 2}
pair2 := [2]int {2, 4}
if pair1 != pair2 {
fmt.Println("different pair")
}
}

As with struct values, arrays cannot be compared using ordering operators <, <=, >, and >=. Attempting to do so will cause a compilation error.

Pointers

Pointer values can be compared for equality but not for ordering. Two pointer values are considered equal if they point to the same value in memory (or if they are nil). For instance, in the following snippet &pair is equal to ptr2 while &pair is not equal ptr.

func main() {
pair := [2]int {4, 2}
ptr := &[2]int {4, 2}
ptr2 := &pair

if &pair != ptr {
fmt.Println("pointing different")
}
if &pair == ptr2 {
fmt.Println("pointing the same")
}
}

Keep in mind that a pointer to a type is not the same as the type itself. Thus Trying to compare the two will cause a type mismatched compilation error.

Interfaces

Interface values are interesting in that they can be compared to

  • not only other interface values
  • but also to values whose types implement the interface

Two interface values are considered equal if their underlying concrete types and their values are comparable and are equal or if both interfaces are nil.

For instance, in the next code snippet, interface values r0 and r2 are equal because they implement the same concrete types with the same values, rectangle{l:3, w:6}. On the other hand, interface values r0 and r1, although they implement the same interface type, are understandably not equal because their concrete values differ, rectangle{3, 6} vs rectangle{6, 3}. Similarly, interface variables r1 and s0 are not equal because they have different dynamic (or concrete) values, although they implement the same interface.

type shape interface {
area() int
}
type rectangle struct {
l int
w int
}
func (r rectangle) area() int {
return r.l * r.w
}
type square struct {
l int
}
func (s square) area() int {
return s.l * s.l
}
func main() {
var r0 shape = rectangle{3, 6}
var r1 shape = rectangle{6, 3}
var r2 shape = rectangle{3, 6}
var s0 shape = square{5}

if r0 == r2 {
fmt.Println("r0 and r2 same shapes")
}

fmt.Println("r1 and s0 equal", (r1 == s0))
}

It is important to note that if the underlying concrete types of the interface values are not comparable (see previous section on topic), any attempt to compare them will cause a runtime panic.

Channels

Channel values can only be compared for equality. Two channel values are considered equal if they originated from the same make call (meaning they refer to the same channel value in memory).

For instance, in the following sample, ch0 is not equal to ch1 even when they have the same types. However, ch1 is equal to ch2 because they both refer to the same channel value.

func main() {
ch0 := make(chan int)
ch1 := make(chan int)
ch2 := ch1

fmt.Println(“ch0 == ch1”, (ch0==ch1))
fmt.Println(“ch1 == ch2”, (ch1==ch2))
}

Conclusion

This post was meant to introduce you to the mechanics of comparing values in Go. The Go programming language has a small set of known rules for comparing and ordering values. As you saw, while values from all types are comparable using the == (or !=) operator, only a few types can be compared using order operators such as <, >, <=, and >=.

If you are a newcomer to Go, I hope this was informative and useful. This writeup is derived from my book Learning Go Programming.

Find me on twitter @vladimirvivien and let me know what you like or what I can improve on.

Happy Go(ding)!