Go Type Assertion
Introduction
Type assertion is a powerful feature in Go that allows you to extract the underlying concrete value from an interface value. In Go, interfaces provide a way to specify behavior without dictating implementation. However, when working with interfaces, you often need to access the concrete value behind them - this is where type assertions come in.
Type assertions allow you to:
- Check if an interface value contains a specific type
- Extract the underlying value from an interface
- Convert between interface types
This concept is fundamental to working effectively with Go's type system, especially when dealing with functions that return or accept interface types like interface{}
(or any
in Go 1.18+).
Understanding Type Assertions
Basic Syntax
The basic syntax for a type assertion in Go is:
value, ok := interfaceValue.(Type)
Where:
interfaceValue
is a variable of an interface typeType
is the concrete type you're assertingvalue
will contain the underlying value if the assertion succeedsok
is a boolean indicating whether the assertion was successful
Simple Example
Let's start with a basic example:
package main
import "fmt"
func main() {
var i interface{} = "hello"
// Type assertion
s, ok := i.(string)
fmt.Println(s, ok) // Output: hello true
// Failed type assertion
n, ok := i.(int)
fmt.Println(n, ok) // Output: 0 false
}
In this example:
- We declare a variable
i
of typeinterface{}
and assign a string value to it - We use type assertion to check if
i
contains a string and extract its value - We attempt another assertion to check if
i
contains an int (which fails)
Type Assertion Variants
Simple Type Assertion (Unsafe)
You can perform a type assertion without checking the success:
package main
import "fmt"
func main() {
var i interface{} = "hello"
// Direct type assertion without checking
s := i.(string)
fmt.Println(s) // Output: hello
// This will panic
// n := i.(int) // panic: interface conversion: interface {} is string, not int
}
Warning: This form is unsafe if you're not certain about the type. If the assertion fails, your program will panic.
Type Assertion with Check (Safe)
The safer approach is to use the two-value form:
package main
import "fmt"
func main() {
var i interface{} = 42
// Safe type assertion with check
if v, ok := i.(int); ok {
fmt.Printf("i contains an int: %d
", v)
} else {
fmt.Println("i does not contain an int")
}
}
Output:
i contains an int: 42
Type Switches
For checking multiple types, Go provides the type switch construct:
package main
import "fmt"
func describe(i interface{}) {
switch v := i.(type) {
case int:
fmt.Printf("Integer: %d
", v)
case string:
fmt.Printf("String: %s
", v)
case bool:
fmt.Printf("Boolean: %v
", v)
default:
fmt.Printf("Unknown type: %T
", v)
}
}
func main() {
describe(42)
describe("hello")
describe(true)
describe(3.14)
}
Output:
Integer: 42
String: hello
Boolean: true
Unknown type: float64
The type switch offers a cleaner way to handle multiple possible types compared to a series of individual type assertions.
Common Use Cases for Type Assertions
Processing Items in a Collection
Type assertions are often used when working with collections of mixed types:
package main
import "fmt"
func printValues(items []interface{}) {
for _, item := range items {
switch v := item.(type) {
case int:
fmt.Printf("Integer: %d
", v)
case string:
fmt.Printf("String: %s
", v)
case float64:
fmt.Printf("Float: %.2f
", v)
default:
fmt.Printf("Unknown type: %T
", v)
}
}
}
func main() {
mixed := []interface{}{42, "hello", 3.14, true}
printValues(mixed)
}
Output:
Integer: 42
String: hello
Float: 3.14
Unknown type: bool
Working with JSON Data
Type assertions are valuable when dealing with dynamic data like JSON:
package main
import (
"encoding/json"
"fmt"
)
func main() {
// A JSON blob with mixed types
jsonBlob := `{"name": "John", "age": 30, "isStudent": false, "scores": [90, 85, 95]}`
var data map[string]interface{}
if err := json.Unmarshal([]byte(jsonBlob), &data); err != nil {
fmt.Println("Error:", err)
return
}
// Access values using type assertions
if name, ok := data["name"].(string); ok {
fmt.Println("Name:", name)
}
if age, ok := data["age"].(float64); ok {
fmt.Println("Age:", int(age)) // JSON numbers become float64
}
if isStudent, ok := data["isStudent"].(bool); ok {
fmt.Println("Is Student:", isStudent)
}
// Working with nested slices
if scores, ok := data["scores"].([]interface{}); ok {
fmt.Print("Scores: ")
for _, score := range scores {
if s, ok := score.(float64); ok {
fmt.Printf("%.0f ", s)
}
}
fmt.Println()
}
}
Output:
Name: John
Age: 30
Is Student: false
Scores: 90 85 95
Error Handling with Custom Error Types
Type assertions are useful for handling specific error types:
package main
import (
"errors"
"fmt"
"os"
)
// Custom error types
type NotFoundError struct {
Item string
}
func (e NotFoundError) Error() string {
return fmt.Sprintf("%s not found", e.Item)
}
// Function that might return different error types
func findItem(name string) error {
if name == "file.txt" {
return os.ErrNotExist
} else if name == "config.json" {
return NotFoundError{Item: name}
} else if name == "" {
return errors.New("empty name provided")
}
return nil
}
func main() {
items := []string{"file.txt", "config.json", "", "data.csv"}
for _, item := range items {
err := findItem(item)
if err != nil {
switch e := err.(type) {
case NotFoundError:
fmt.Printf("Custom not found error: %v
", e)
case *os.PathError:
fmt.Printf("OS path error: %v
", e)
default:
if err == os.ErrNotExist {
fmt.Printf("Item doesn't exist: %v
", err)
} else {
fmt.Printf("Other error: %v
", err)
}
}
} else {
fmt.Printf("Found item: %s
", item)
}
}
}
Output:
Item doesn't exist: file does not exist
Custom not found error: config.json not found
Other error: empty name provided
Found item: data.csv
Type Assertion vs Type Conversion
It's important to understand the difference between type assertion and type conversion:
package main
import "fmt"
func main() {
// Type conversion (between compatible types)
var i int = 42
var f float64 = float64(i)
fmt.Println(f) // Output: 42
// Type assertion (for interface types)
var x interface{} = "hello"
s := x.(string)
fmt.Println(s) // Output: hello
// This won't compile - can't use type assertion on non-interface types
// str := i.(string) // Invalid: i is not an interface type
// This won't compile - can't use type conversion with interfaces
// str2 := string(x) // Invalid: cannot convert x (type interface{}) to type string
}
Remember:
- Type conversion (
T(v)
) works between compatible concrete types - Type assertion (
v.(T)
) works only on interface types to retrieve their underlying value
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)