Golang Type Assertion Explained with Examples

Golang Type Assertion Explained with Examples

Getting started with golang Type Assertion

Atype assertionis an operation applied to an interface value. Syntactically, it looks likex.(T), wherexis an expression of an interface type andTis 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 typeTis a concrete type, then the type assertion checks whetherx’s dynamic type isidentical toT. If this check succeeds, the result of the type assertion isx’s dynamic value, whose type is of courseT. 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 typeTis an interface type, then the type assertion checks whetherx’s dynamic typesatisfiesT. 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 typeT.

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 firsttype assertionbelow, bothwandrwholdos.Stdoutso each has a dynamic type of*os.File, butw, anio.Writer, exposes only the file’sWritemethod, whereasrwexposes itsReadmethod 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, thetype assertionfails. Atype assertionto 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 and Switch key word to compare the type of underlying interface’s data

Summary

Atype assertionis amechanism 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 assertionsuse thex.(T)notation, wherexis an interface type andTis a type, and help you extract the value that is hidden behind the empty interface. For a typeassertion to work,xshould not beniland the dynamic type ofxshould be identical to theTtype.


References

https://pkg.go.dev/fmt

Tuan Nguyen

Tuan Nguyen

Data Scientist

Proficient in Golang, Python, Java, MongoDB, Selenium, Spring Boot, Kubernetes, Scrapy, API development, Docker, Data Scraping, PrimeFaces, Linux, Data Structures, and Data Mining. With expertise spanning these technologies, he develops robust solutions and implements efficient data processing and management strategies across various projects and platforms.