Getting started with golang Type Assertion
A type assertion is an operation applied to an interface value. Syntactically, it looks like x.(T)
, where x
is an expression of an interface type and T
is a type, called the “asserted” type. A type assertion checks that the dynamic type of its operand matches the asserted type.
There are two possibilities. First, if the asserted type T
is a concrete type, then the type assertion checks whether x’s dynamic type is identical to T
. If this check succeeds, the result of the type assertion is x’s dynamic value, whose type is of course T
. In other words, a type assertion to a concrete type extracts the concrete value from its operand. If the check fails, then the operation panics. For example:
var w io.Writer
w = os.Stdout
f := w.(*os.File) // success: f == os.Stdout
c := w.(*bytes.Buffer) // panic: interface holds *os.File, not *bytes.Buffer
Second, if instead the asserted type T
is an interface type, then the type assertion checks whether x’s dynamic type satisfies T
. If this check succeeds, the dynamic value is not extracted; the result is still an interface value with the same type and value components, but the result has the interface type T
.
In other words, a type assertion to an interface type changes the type of the expression, making a different (and usually larger) set of methods accessible, but it preserves the dynamic type and value components inside the interface value.
After the first type assertion below, both w
and rw
hold os.Stdout
so each has a dynamic type of *os.File
, but w
, an io.Writer
, exposes only the file’s Write method, whereas rw
exposes its Read method too.
var w io.Writer
w = os.Stdout
rw := w.(io.ReadWriter) // success: *os.File has both Read and Write
w = new(ByteCounter)
rw = w.(io.ReadWriter) // panic: *ByteCounter has no Read method
No matter what type was asserted, if the operand is a nil interface value, the type assertion fails. A type assertion to a less restrictive interface type (one with fewer methods) is rarely needed, as it behaves just like an assignment, except in the nil case.
w = rw // io.ReadWriter is assignable to io.Writer
w = rw.(io.Writer) // fails only if rw == nil
Example 1: Simple example of using type assertion in Golang
The code shown below using type assertion to check the concrete type of an interface:
package main
import (
"fmt"
)
// main function
func main() {
// an interface which has a string value
var checkInterface interface{} = "GoLinuxCloud"
// assigning value of interface type to checkType variable
checkType := checkInterface.(string)
// printing the concrete value
fmt.Println(checkType)
// panic because interface does not have int type
checkTypeInt := checkInterface.(int)
fmt.Println(checkTypeInt)
}
Output:
GoLinuxCloud
panic: interface conversion: interface {} is string, not int
goroutine 1 [running]:
main.main()
C:/Users/H518272/Desktop/golang/main/milestone41.go:20 +0x78
exit status 2
Explanation:
- The variable
checkInterface
is interface{} type which means it can hold any type. - The concrete type that is assigned to the variable is string
- If the type assertion is against the
int
type, running this program will produce a run-time error because we have wrong type assertion.
Example 2: Check Type Assertion status using ok comma idiom
The below example use ok comma idiom to check assertion is successful or not:
package main
import (
"fmt"
)
func main() {
// an interface has a floaf64 value
var checkInterface interface{} = 11.04
// assigning value of interface type to checkType variable
checkType := checkInterface.(float64)
// printing the concrete value
fmt.Println(checkType)
// test whether interface has string type and
// return true if found or
// false otherwise
checkType2, ok := checkInterface.(string)
if ok {
fmt.Println("Correct assertion!")
fmt.Println(checkType2)
} else {
fmt.Println("Wrong assertion!")
}
}
Output:
11.04
Wrong assertion!
Example 3: Logging type assertion errors
We can modify the code in example 3 a little bit to log more information when type assertion has errors. One way is we use the printf format string %T
that produces the type.
checkType2, ok := checkInterface.(string)
if ok {
fmt.Println("Correct assertion!")
fmt.Println(checkType2)
} else {
fmt.Printf("Wrong assertion, got data of type %T but wanted String!", checkInterface)
}
Example 4: Using Type Switch to determine type of interface
What happens when you do not know the data type before attempting a type assertion? How can you differentiate between the supported data types and the unsupported ones? How can you choose a different action for each supported data type?
The answer is by using type switches. Type switches use switch blocks for data types and allow you to differentiate between type assertion values, which are data types, and process each data type the way you want. On the other hand, in order to use the empty interface in type switches, you need to use type assertions.
In this example, we will use Switch key word to determine interface's type.
package main
import (
"fmt"
)
func main() {
var testInterface interface{} = "GolinuxCLoud"
var testInterface2 interface{} = map[string]int{"Anna": 4, "Bob": 10, "Clair": 11}
switch testInterface.(type) {
case string:
fmt.Println(testInterface, "is a string!")
case int:
fmt.Println(testInterface, "is an int!")
case float64:
fmt.Println(testInterface, "is a float64!")
default:
fmt.Println(testInterface, "is not basic type!")
}
switch testInterface2.(type) {
case string:
fmt.Println(testInterface2, "is a string!")
case int:
fmt.Println(testInterface2, "is an int!")
case float64:
fmt.Println(testInterface2, "is a float64!")
default:
fmt.Println(testInterface2, "is not basic type!")
}
}
Output:
GolinuxCLoud is a string!
map[Anna:4 Bob:10 Clair:11] is not basic type!
Explanation:
- Create 2 interface: one contains a string and one contains a map
- Use the
.(type)
function andSwitch
key word to compare the type of underlying interface's data
Summary
A type assertion is a mechanism for working with the underlying concrete value of an interface. This mainly happens because interfaces are virtual data types without their own values—interfaces just define behavior and do not hold data of their own.
Type assertions use the x.(T)
notation, where x
is an interface type and T
is a type, and help you extract the value that is hidden behind the empty interface. For a type assertion to work, x
should not be nil and the dynamic type of x
should be identical to the T
type.
References