Search code examples
gointerfacecomparison

Comparison between empty interfaces in Golang


According to the specification:

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.

var err error 
var reader io.Reader                           

As far as understand, err and reader have different dynamic types (error and io.Reader) and therefore are not comparable.

fmt.Println(err == reader) 

will cause a compile error:

invalid operation: err == reader (mismatched types error and io.Reader)

If it is true, why Println command outputs the same results for both variables? Why both are nil?

fmt.Printf("reader: %T", reader) // nil
fmt.Printf("error: %T", err) // nil

EDIT reflect.TypeOf(err)or reflect.TypeOf(reader) will also output nil. I do not understand why the output is the same if the types are different.


Solution

  • It's true that interface values are comparable, but you can only compare values that are assignable to each other (more precisely, one is assignable to the other). Quoting from Spec: Comparison operators:

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

    You can't assign an error value to an io.Reader, and you can't assign an io.Reader value to an error either, so you can't compare them.

    They may or may not store the same dynamic value, if you want to compare those, first covert both to interface{}, so you can compare them, e.g.:

    fmt.Println(interface{}(err) == interface{}(reader))
    

    This will output (try it on the Go Playground):

    true
    

    Note: actually it would be enough to convert only one of them to interface{}, because that way the the other value will become comparable to the type of the one you converted to interface{} (any value is convertible to interface{}), so it would also be enough to do:

    fmt.Println(interface{}(err) == reader)
    

    Testing the comparison with non-nil interface values:

    type x int
    
    func (x) Error() string            { return "" }
    func (x) Read([]byte) (int, error) { return 0, nil }
    
    err = x(0)
    reader = x(0)
    fmt.Println(interface{}(err) == interface{}(reader))
    
    reader = x(1)
    fmt.Println(interface{}(err) == interface{}(reader))
    

    Now output will be (try it on the Go Playground):

    true
    false
    

    Also don't forget that a nil interface value does not equal to a non-nil interface value holding a nil dynamic value. For details, see Hiding nil values, understanding why golang fails here

    Edit:

    The fmt package prints the value inside the interface, not the interface value. Quoting from package doc of fmt:

    Regardless of the verb, if an operand is an interface value, the internal concrete value is used, not the interface itself.

    Same goes with reflect.TypeOf(): it returns the dynamic type, but if you pass a nil interface value to it, it returns nil so the fmt package will print nil. Quoting its doc:

    TypeOf returns the reflection Type that represents the dynamic type of i. If i is a nil interface value, TypeOf returns nil.