Go edit forum

Empty interface

Technically speaking, an empty interface (interface{}) is an interface with no methods.

What follows from that is that every type conforms to interface{}.

In practice, empty interface is Go’s version of object type in Java or C# in that it combines a type and its value.

Empty interface is effectively a dynamic type in a static language.

Empty interface is also a way to implement union types in Go.

Since every type conforms to interface{}, you can assign any value to a variable of interface{} type.

At that point, you can no longer tell what is the real type at compile time.

Zero value of empty interface is nil.

Basic example:

func printVariableType(v interface{}) {
	switch v.(type) {
	case string:
		fmt.Printf("v is of type 'string'\n")
	case int:
		fmt.Printf("v is of type 'int'\n")
	default:
		// generic fallback
		fmt.Printf("v is of type '%T'\n", v)
	}
}

func main() {
	printVariableType("string") // string
	printVariableType(5)        // int
	printVariableType(int32(5)) // int32
}
v is of type 'string'
v is of type 'int'
v is of type 'int32'

 

At compile time, when you have a variable whose type is interface (including empty interface) you don’t know what is the real, underlying type.

You can access underlying type at runtime using type assertion.

func printTypeAndValue(iv interface{}) {
	if v, ok := iv.(string); ok {
		fmt.Printf("iv is of type string and has value '%s'\n", v)
		return
	}
	if v, ok := iv.(int); ok {
		fmt.Printf("iv is of type int and has value '%d'\n", v)
		return
	}
	if v, ok := iv.(*int); ok {
		fmt.Printf("iv is of type *int and has value '%s'\n", v)
		return
	}
}

func panicOnInvalidConversion() {
	var iv interface{} = "string"

	v := iv.(int)
	fmt.Printf("v is int of value: %d\n", v)
}

func main() {
	// pass a string
	printTypeAndValue("string")
	i := 5
	// pass an int
	printTypeAndValue(i)
	// pass a pointer to int i.e. *int
	printTypeAndValue(&i)

	panicOnInvalidConversion()
}
iv is of type string and has value 'string'
iv is of type int and has value '5'
iv is of type *int and has value '%!s(*int=0xc420016028)'
panic: interface conversion: interface {} is string, not int

goroutine 1 [running]:
main.panicOnInvalidConversion()
	/tmp/375769756/main.go:25 +0x45
main.main()
	/tmp/375769756/main.go:38 +0xb8
exit status 2

Type assertion

Type assertion allows you to check if empty interface value is of a given type.

For completness, you can use short version of type switch: v := iv.(int) (vs. v, ok := iv.(int)).

The difference is that the short version will panic if iv is not of the asserted type:

func panicOnInvalidConversion(iv interface{}) {
	v := iv.(int)
	fmt.Printf("v is int of value: %d\n", v)
}

func main() {
	panicOnInvalidConversion("string")
}
panic: interface conversion: interface {} is string, not int

goroutine 1 [running]:
main.panicOnInvalidConversion(0x493920, 0x4c4410)
	/tmp/013892416/main.go:8 +0xc7
main.main()
	/tmp/013892416/main.go:13 +0x39
exit status 2

As a rule of thumb, you shouldn’t try to discover underlying value of interface type as it pierces through an abstraction.

Type switch

A switch statement can dispatch based on the type of the value wrapped by the interface.

If you have an interface value you can switch based on the type of the underlying value:

func smartConvertToInt(iv interface{}) (int, error) {
	// inside case statements, v is of type matching case type
	switch v := iv.(type) {
	case int:
		return v, nil
	case string:
		return strconv.Atoi(v)
	case float64:
		return int(v), nil
	default:
		return 0, fmt.Errorf("unsupported type: %T", iv)
	}
}

func printSmartConvertToInt(iv interface{}) {
	i, err := smartConvertToInt(iv)
	if err != nil {
		fmt.Printf("Failed to convert %#v to int\n", iv)
		return
	}
	fmt.Printf("%#v of type %T converted to %d\n", iv, iv, i)
}

func main() {
	printSmartConvertToInt("5")
	printSmartConvertToInt(4)
	printSmartConvertToInt(int32(8))
	printSmartConvertToInt("not valid int")
}
"5" of type string converted to 5
4 of type int converted to 4
Failed to convert 8 to int
Failed to convert "not valid int" to int
  ↑ ↓ to navigate     ↵ to select     Esc to close