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:
- Runtime type checking for interfaces
- Type-safe conversions with ok idiom
- Polymorphic behavior without inheritance
- Error type checking for error handling
- Flexible function signatures with interface{}
These features enable powerful polymorphism in Go.
Comments