Context

Recently I stumbled upon an issue where a comparison operator was not behaving the way I supposed it would behave.

import "fmt"

func IsEmpty(s interface{}) bool {
	return s == ""
}

type AliasString string

var s string
var s2 AliasString

func main() {
	fmt.Println(IsEmpty(s)) // true
	fmt.Println(IsEmpty(s2)) // I Expected: true, Returned: false
	fmt.Println(s2 == "") // Based on above should be false as well, but returns true
}

When this happend the first thought that came in my mind was this.

The issue we were facing was similar, let me explain.

Go Spec

To understand why this happens let’s read though how Go Spec specifies Comparison Operators

Assignable

The spec says:

In any comparison, the first operand must be assignable to the type of the second operand, or vice versa.

Now let’s create an example to test if an empty string ("") is assignable to our AliasString type.

package main

import "fmt"

type AliasString string

func main() {
	var s String = ""

	fmt.Println(s)
}

Looks like the code runs fine which means it’s assignable. Let’s look elsewhere now.

Comparable

In our IsEmpty method we are accepting interface{} and comparing it to an empty string. So, let’s only focus on the part of the spec that specifies that.

Interface values are comparable. Two interface values are equal if they have identical dynamic types and equal dynamic values or if both have value nil.

Let’s learn more about identical.

Identical

From the spec,

Two types are either identical or different.

A defined type is always different from any other type

Now these two statement looks to be of interest for us. There are also some examples presented in spec which sheds some more light it.

From spec

type (
	A0 = []string
	A1 = A0
	A2 = struct{ a, b int }
	A3 = int
	A4 = func(A3, float64) *A0
	A5 = func(x int, _ float64) *[]string
)

type (
	B0 A0
	B1 []string
	B2 struct{ a, b int }
	B3 struct{ a, c int }
	B4 func(int, float64) *B0
	B5 func(x int, y float64) *A1
)

type	C0 = B0

B0 and B1 are different because they are new types created by distinct type definitions; func(int, float64) *B0 and func(x int, y float64) *[]string are different because B0 is different from []string.

Ok based on that it means:

AliasString != string

Conclusion

So, based on all we have read, no two defined types are equal in Golang and hence empty AliasString != empty string. But the last time works because the go compiler doesn’t treat ("") as empty string but treats it as empty AliasString and thus they are equal.

Shout-Outs

Many thanks to Rohit Sudebi, Gregor Best and Joe Davidson for helping me with understand this problem.