Empty interface
suggest changeTechnically 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=0xc000016038)'
panic: interface conversion: interface {} is string, not int
goroutine 1 [running]:
main.panicOnInvalidConversion()
/tmp/src158944484/type assertion.go:28 +0x45
main.main()
/tmp/src158944484/type assertion.go:41 +0x9a
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(0x4a01e0, 0x4db270)
/tmp/src764701939/type assertion.go:11 +0xd6
main.main()
/tmp/src764701939/type assertion.go:16 +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