This series is about understanding and documenting “The Go Programming Language”.

Type Assertion

Type Assertion is an operation on an interface value. The syntax is x.(T), where x is an expression for interface Type and T is an asserTD Type, which is asserted.

There are two main scenarios for the use of assertions: If asserted Type is a concrete type, an instance class type, the assertion will check whether the dynamic type of X is the same as that of T. If so, the assertion will result in the dynamic value of X. And of course the type of dynamic value is T. In other words, the assertion to concrete Type is actually getting the dynamic value of X.

If asserted Type is an interface type, the purpose of assertion is to check whether the dynamic type of X satisfies T. If so, the assertion result is an expression satisfying T. But the dynamic type and dynamic value are the same as x. In other words, the assertion of interface type actually changes the type of X, usually the interface type of a larger Method set, but keeps the original Dynamic Type and dynamic value.

Let’s look at two examples.

case 1

package main import ( "fmt" "io" "os" ) func main() { var w io.Writer w = os.Stdout w.Write([]byte("hello Go!" )) fmt.Printf("%T\n", w) fw := w.(*os.File) fmt.Printf("%T\n", fw) }Copy the code

In the above code, w is an interface expression with Write method, and its dynamic value is OS.stdout, Declare w.(* os.file) for Concrete type * os.file, then f is the dynamic value os.stdout of W.

case 2

package main import ( "fmt" "io" "os" ) func main() { var w io.Writer w = os.Stdout w.Write([]byte("hello Go!" )) fmt.Printf("%T\n", w) rw := w.(io.ReadWriter) fmt.Printf("%T\n", rw) }Copy the code

W.(IO.ReadWriter) for interface type IO.ReadWriter, then rW is an interface value whose dynamic value is * os.file.

For concrete Type or Interface type, an assert will fail if its expression is nil.

var w io.Writer
fw := w.(*os.File) //fail
rw := w.(io.ReadWriter) //failCopy the code

Usually we just want to know what kind of concrete type dynamic value is, using the OK expression.

var w io.Writer = os.Stdout f, ok := w.(*os.File) // success: ok, f == os.Stdout b, ok := w.(*bytes.Buffer) // failure: ! ok, b == nilCopy the code

In ok, nil does not cause assertion failure. Ok is true if assertion is successful and false otherwise. The other variable is zero value of asserted Type if assertion fails.

OK expressions are often used in if statements:

if f, ok := w.(*os.File); ok { // ... use f... }Copy the code

Type Switches

Interface is generally used in these two situations. One is like IO.Reader and IO.Writer. The real meaning of an Interface method is to express the similarity of different concrete types implementing this Interface. This means that the expressiveness of the interface method is in full play here. Focus on method, not Concrete type.

One way is to use interface to store the ability of different concrete types and do different processing according to different concrete types when necessary. This can be done by using the interface assertion to determine the type of dynamic Type. Focus on concrete type, not method.

A Type switch is an assertion that leverages the interface’s ability to store different Concrete types.

switch x.(type) {
    case nil:
case int, uint:
case bool:
case string:
default:
}Copy the code

This type of statement is called type switch where X is interface expression and asserted Type is a type literal. Each case statement can have one or more types. Nil case matches if x == nil, default case matches if there is no type match.

Sometimes we need to use dynamic value in type switch, which requires type assertion to extract the dynamic value of the interface. Switch x := x.(type){}

package main

import (
    "fmt"
)

func main() {
    var x1 interface{}
    var x2 int
    var x3 string
    var x4 bool

    fmt.Println(sqlQuote(x1))
    fmt.Println(sqlQuote(x2))
    fmt.Println(sqlQuote(x3))
    fmt.Println(sqlQuote(x4))
}

func sqlQuote(x interface{}) string {
    switch x := x.(type) {
    case nil:
        return "null"
    case int, uint:
        return fmt.Sprintf("%d", x)
    case bool:
        if x {
            return "true"
        }

        return "false"
    case string:
        return "string"
    default:
        panic("no match case")
    }
}Copy the code

The extracted value assigned to X in the above example overshadows the assertion expression x in the Switch block, but does not affect the use of x in function because switch, like for, is a block scope.