Type assertions and type switches in Golang

Go focuses on static typing, ensures type safety, and prevents runtime errors. But what if we need to work with data whose type might be unknown at compile-time? In this situation, type assertions and type switches come in handy!

In Go interfaces, powerful constructs define a set of methods a type must implement. An interface variable can hold a value of any type that implements the interface. This flexibility is crucial when dealing with dynamic data.

Type assertions

A type assertion allows us to extract the underlying concrete value from an interface variable, assuming it holds the type:

value, ok := interface_type.(string)
  • interface_type is an interface for which we want to check the type assertion.

  • value contains the return type of the interface.

  • ok is a variable that holds any error that might occur during type assertion.

Note:

  • Incorrect type assertion: Asserting to the wrong type will cause a panic at runtime. Always check the ok value before using the asserted variable.

  • Type safety: Type assertions bypass static type checking. Use them cautiously and only when necessary.

Example: Type assertion

Click the “Run” button to execute the example:

package main
import "fmt"
func main() {
var i interface{} = "hello"
// Type assertion
str, ok := i.(string)
if ok {
fmt.Println("String:", str)
} else {
fmt.Println("Not a string")
}
// Type assertion with wrong type
_, ok = i.(int)
if !ok {
fmt.Println("Not an integer")
}
}
  • Line 6: Here, we declare a variable i of type interface{}. This means that i can hold any type of value since interface{} is the empty interface in Go, which specifies zero methods.

  • Line 9: We perform a type assertion on the variable i. We check whether the dynamic type of i is a string or not. If the assertion holds true, the underlying value of i is assigned to the variable str, and ok will be true. If the assertion fails, str will be assigned the zero value of the string type, and ok will be false.

  • Lines 10–14: We use the boolean variable ok to determine whether the type assertion succeeded or not. If ok is true, it means that i is indeed a string, so we print out the value of str using fmt.Println("String:", str). Otherwise, if ok is false, it means that i is not a string, so we print out “Not a string.”

  • Line 17: This line performs another type assertion on the variable i, this time checking if i is of type int. Since we’re not interested in the actual value (if any), we use the blank identifier _ to discard it. We assign the result of the type assertion to ok again.

  • Lines 18–20: Here, we check if ok is false, meaning that the type assertion failed. If so, we print out “Not an integer,” indicating that the value held by i is not an integer.

Type switches

A type switch is a control flow structure specifically designed for working with interfaces. It performs a series of type assertions in a more structured way. It allows testing the interface value against multiple types, executing the corresponding case block for the first matching type:

switch variable.(type) {
case type1:
// Code to handle type1
case type2:
// Code to handle type2
default:
// Code to handle unknown type
}
  • variable is the interface whose type needs to be checked.

  • In this switch statement, we have three cases; type, type2, and default. The matching case will be executed based on the type of interface.

Example: Type switches

Click the “Run” button to execute the example:

package main
import "fmt"
func doSomething(i interface{}) {
switch v := i.(type) {
case int:
fmt.Println("Double of integer:", v*2)
case string:
fmt.Println("Length of string:", len(v))
default:
fmt.Println("Unknown type")
}
}
func main() {
doSomething(10) // Output: Double of integer: 20
doSomething("hello") // Output: Length of string: 5
doSomething(3.14) // Output: Unknown type
}
  • Line 5: The doSomething function takes an empty interface (interface{}) as a parameter. This means that i can hold values of any type.

  • Line 6: This is a type switch statement. It checks the type of the interface value i dynamically and assigns the value of i to a new variable v whose type is determined by the type of i. The keyword type is used within the switch statement to indicate that it’s a type switch. Each case within the switch statement checks against a specific type.

  • Lines 7–8: If the type of i is int, this case block is executed. In this case, it prints “Double of integer:” followed by the doubled value of v.

  • Lines 9–10: If the type of i is string, this case block is executed. It prints “Length of string:” followed by the length of the string v.

  • Lines 11–12: If the type of i doesn’t match any of the specified cases, the default case is executed. It prints “Unknown type.”

  • Line 16: This is the main() function where the program execution starts.

  • Line 17: This calls the doSomething function with an integer value 10 as an argument. Since 10 is an integer, the first case block (case int) is executed, printing “Double of integer: 20.”

  • Line 18: This calls the doSomething function with a string value "hello" as an argument. Since "hello" is a string, the second case block (case string) is executed, printing “Length of string: 5.”

  • Line 19: This calls the doSomething function with a floating-point value 3.14 as an argument. Since 3.14 is neither an integer nor a string, the default case is executed, printing “Unknown type.”

Comparison between Type Assestions and Type Switches

Aspect

Type Assertions

Type Switches

Purpose

Test and extract the underlying type of an interface

Perform conditional logic based on interface type

Usage

Used when specific type information is required

Ideal for scenarios with multiple type possibilities

Handling non-existent types

Requires additional error checking (the ok flag)

Automatically handles non-existent types

Error handling

Requires explicit handling for non-matching types

Simplifies handling with default case in switch

Type information

Extracts and provides access to specific type

Allows direct access to type within case statements

Readability

May require additional error handling, less concise

Provides clearer and more concise code structure

Flexibility

Offers fine-grained control over type extraction

Enables concise and structured type-based branching

Conclusion

Type assertions and type switches are essential tools for working with interfaces in Go. They provide flexibility and allow for dynamic type handling, enabling developers to write more expressive and concise code. By mastering these concepts, a developer will be better equipped to design robust and efficient Go programs.

Copyright ©2024 Educative, Inc. All rights reserved