Skip to main content
โšก Calmops

Type Assertions and Type Switches

Type Assertions and Type Switches

Type assertions and type switches enable runtime type checking in Go. They’re essential for working with interfaces and polymorphism.

Type Assertions

Basic Type Assertion

package main

import "fmt"

func main() {
    var i interface{} = "hello"

    // Type assertion
    s := i.(string)
    fmt.Println(s)  // hello

    // Type assertion with ok
    s, ok := i.(string)
    if ok {
        fmt.Println("String:", s)
    }

    // Type assertion to wrong type
    n, ok := i.(int)
    if !ok {
        fmt.Println("Not an int")
    }
}

Asserting to Concrete Type

package main

import "fmt"

type Reader interface {
    Read() string
}

type FileReader struct {
    name string
}

func (fr FileReader) Read() string {
    return fmt.Sprintf("Reading from %s", fr.name)
}

func main() {
    var r Reader = FileReader{name: "data.txt"}

    // Assert to concrete type
    if fr, ok := r.(FileReader); ok {
        fmt.Println("File:", fr.name)
    }
}

Type Switches

Basic Type Switch

package main

import "fmt"

func describe(i interface{}) {
    switch v := i.(type) {
    case int:
        fmt.Printf("Integer: %d\n", v)
    case string:
        fmt.Printf("String: %s\n", v)
    case bool:
        fmt.Printf("Boolean: %v\n", v)
    case float64:
        fmt.Printf("Float: %.2f\n", v)
    default:
        fmt.Printf("Unknown type: %T\n", i)
    }
}

func main() {
    describe(42)
    describe("hello")
    describe(true)
    describe(3.14)
    describe([]int{1, 2, 3})
}

Type Switch with Interfaces

package main

import "fmt"

type Writer interface {
    Write(string) error
}

type Reader interface {
    Read() string
}

type FileHandler struct {
    name string
}

func (fh FileHandler) Write(data string) error {
    fmt.Printf("Writing to %s: %s\n", fh.name, data)
    return nil
}

func (fh FileHandler) Read() string {
    return fmt.Sprintf("Reading from %s", fh.name)
}

func process(i interface{}) {
    switch v := i.(type) {
    case Writer:
        v.Write("data")
    case Reader:
        fmt.Println(v.Read())
    default:
        fmt.Println("Unknown type")
    }
}

func main() {
    fh := FileHandler{name: "file.txt"}
    process(fh)
}

Practical Patterns

Handling Multiple Types

package main

import "fmt"

func processValue(v interface{}) {
    switch val := v.(type) {
    case int:
        fmt.Printf("Integer: %d, doubled: %d\n", val, val*2)
    case string:
        fmt.Printf("String: %s, length: %d\n", val, len(val))
    case []int:
        sum := 0
        for _, n := range val {
            sum += n
        }
        fmt.Printf("Array sum: %d\n", sum)
    case nil:
        fmt.Println("Value is nil")
    default:
        fmt.Printf("Unknown type: %T\n", v)
    }
}

func main() {
    processValue(42)
    processValue("hello")
    processValue([]int{1, 2, 3, 4, 5})
    processValue(nil)
}

Type Assertion with Fallback

package main

import "fmt"

func getString(v interface{}) string {
    // Try string
    if s, ok := v.(string); ok {
        return s
    }

    // Try int
    if i, ok := v.(int); ok {
        return fmt.Sprintf("%d", i)
    }

    // Try float64
    if f, ok := v.(float64); ok {
        return fmt.Sprintf("%.2f", f)
    }

    // Default
    return fmt.Sprintf("%v", v)
}

func main() {
    fmt.Println(getString("hello"))
    fmt.Println(getString(42))
    fmt.Println(getString(3.14))
    fmt.Println(getString(true))
}

Checking Interface Implementation

package main

import "fmt"

type Reader interface {
    Read() string
}

type Writer interface {
    Write(string) error
}

type FileHandler struct{}

func (fh FileHandler) Read() string {
    return "data"
}

func (fh FileHandler) Write(data string) error {
    return nil
}

func main() {
    fh := FileHandler{}

    // Check if implements Reader
    if _, ok := interface{}(fh).(Reader); ok {
        fmt.Println("Implements Reader")
    }

    // Check if implements Writer
    if _, ok := interface{}(fh).(Writer); ok {
        fmt.Println("Implements Writer")
    }
}

Error Type Assertions

Checking Error Types

package main

import (
    "errors"
    "fmt"
    "os"
)

func main() {
    _, err := os.Open("nonexistent.txt")

    // Type assertion to *os.PathError
    if pathErr, ok := err.(*os.PathError); ok {
        fmt.Printf("Path error: %s\n", pathErr.Path)
    }

    // Using errors.As (Go 1.13+)
    var pathErr *os.PathError
    if errors.As(err, &pathErr) {
        fmt.Printf("Path: %s\n", pathErr.Path)
    }
}

Best Practices

โœ… Good: Always Check Type Assertion

// DO: Check type assertion result
if str, ok := v.(string); ok {
    fmt.Println(str)
} else {
    fmt.Println("Not a string")
}

โŒ Bad: Assume Type Assertion Succeeds

// DON'T: Assume type assertion succeeds
str := v.(string)  // Panic if not string

โœ… Good: Use Type Switch for Multiple Types

// DO: Use type switch for multiple types
switch v := i.(type) {
case int:
    // handle int
case string:
    // handle string
default:
    // handle other
}

โœ… Good: Document Type Requirements

// DO: Document what types are expected
// Process handles int, string, and []int
func Process(v interface{}) {
    switch v.(type) {
    case int, string, []int:
        // process
    }
}

Summary

Type assertions and type switches provide:

  1. Runtime type checking for interfaces
  2. Type-safe conversions with ok idiom
  3. Polymorphic behavior without inheritance
  4. Error type checking for error handling
  5. Flexible function signatures with interface{}

These features enable powerful polymorphism in Go.

Comments