Dominik Honnef

Go: On implicit type conversions, type identity and a little gotcha

A well known design decision in the Go programming language is the absence of implicit type conversions. An int is always an int and not an int32 and certainly not a float. And a net.IP is not a []byte… or is it?

Knowing only the “no implicit type conversions” principle, the following code should not compile:

package main

import (
    "fmt"
    "net"
)

func main() {
    var foo net.IP
    var bar []byte = []byte{1, 2, 3, 4}
    foo = bar

    fmt.Println(foo)
}

But it does! Now, the definition of net.IP is type IP []byte, but we also know that this should be an entirely new type and not an alias, because Go doesn’t have type aliases, either. So why does this compile and execute properly?

The secret lies in Go’s understanding of type identity and assignability. The following part is of special importance:

A value x is assignable to a variable of type T (“x is assignable to T”) in any of these cases:

[…]

  • x’s type V and T have identical underlying types and at least one of V or T is not a named type.

And in our case, []byte is not a named type (unlike byte or net.IP, which are), which means that values of type net.IP and []byte can be freely assigned to each other. And of course the same applies to all other identical scenarios, net.IP and []byte are just examples.

The remaining question is: Why did they add this special case to the language specification? Go is not a language of odd exceptions, but at first glance, this exception does seem very odd. The only sensible explanation I came across are function types. Consider the following code:

package main

import "fmt"

type BinaryOp func(int, int) int

func Do(fun BinaryOp, a, b int) int {
    return fun(a, b)
}

func main() {
    result := Do(func(a, b int) int { return a + b },
        1, 2)
    fmt.Println(result)
}

If BinaryOp (a named type) and func(int, int) int (an unnamed type) weren’t identical, writing anonymous functions would be quite a bit uglier.

I don’t know if the initial example, identity of named and unnamed slice types, was intentional or an accident caused by an overly broad specification, but I’d recommend not making use of it and sticking to explicit conversions, even if they’re not necessary. Clarity of code trumps saving a few keystrokes.