Type Assertions and Reflection Boundaries
Type Assertions and Reflection Boundaries
Overview
Type assertions extract concrete types from interfaces. Reflection provides runtime type inspection but should be used sparingly.
Type Assertions
var i interface{} = "hello"
s := i.(string) // Panic if wrong type
s, ok := i.(string) // Safe: ok is false if wrong type
if s, ok := i.(string); ok {
fmt.Println(s)
}Type Switches
func describe(i interface{}) {
switch v := i.(type) {
case int:
fmt.Println("int:", v*2)
case string:
fmt.Println("string:", len(v))
case bool:
fmt.Println("bool:", !v)
default:
fmt.Printf("unknown: %T\n", v)
}
}The reflect Package
import "reflect"
v := 42
t := reflect.TypeOf(v) // int
val := reflect.ValueOf(v) // 42
fmt.Println(t.Name()) // "int"
fmt.Println(t.Kind()) // int
fmt.Println(val.Int()) // 42Struct Reflection
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
u := User{Name: "Alice", Age: 30}
t := reflect.TypeOf(u)
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fmt.Printf("%s: %s\n", field.Name, field.Tag.Get("json"))
}When to Use Reflection
✅ Good uses: - Serialization (JSON, XML) - ORM field mapping - Generic testing utilities - Dependency injection
❌ Avoid for: - Performance-critical code - Simple type checking - Normal business logic
Performance Cost
// Direct access: ~1ns
user.Name
// Reflection: ~100ns+
reflect.ValueOf(user).FieldByName("Name").String()Prefer Generics Over Reflection
// Go 1.18+: Use generics instead of reflect
func PrintSlice[T any](s []T) {
for _, v := range s {
fmt.Println(v)
}
}Summary
| Approach | Use Case |
|---|---|
| Type assertion | Extract known concrete type |
| Type switch | Handle multiple types |
| reflect | Runtime introspection (last resort) |
| Generics | Compile-time type safety |
More examples
Example: safe comma-ok assertion
Save as main.go and go run . (with go mod init example if needed).
package main
import "fmt"
func asInt(v any) {
if n, ok := v.(int); ok {
fmt.Println("int:", n)
return
}
fmt.Printf("not int: %T\n", v)
}
func main() {
asInt(7)
asInt("nope")
}Expected:
int: 7
not int: string
Example: type switch
Save as main.go and go run . (with go mod init example if needed).
package main
import "fmt"
func classify(v any) {
switch x := v.(type) {
case int:
fmt.Println("int", x*2)
case string:
fmt.Println("string len", len(x))
default:
fmt.Printf("other %T\n", x)
}
}
func main() {
classify(3)
classify("go")
classify(true)
}Expected:
int 6
string len 2
other bool
Runnable example
Save as main.go. Then:
go mod init example
go run .package main
import (
"fmt"
"reflect"
)
func describe(i any) {
switch v := i.(type) {
case int:
fmt.Println("int:", v*2)
case string:
fmt.Println("string len:", len(v))
case bool:
fmt.Println("bool not:", !v)
default:
fmt.Printf("unknown: %T\n", v)
}
}
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
var i any = "hello"
s, ok := i.(string)
fmt.Println("assert string:", s, ok)
n, ok := i.(int)
fmt.Println("assert int:", n, ok)
describe(21)
describe("go")
describe(true)
describe(3.14)
// Reflection: useful for tags/serialization; prefer assertions/generics elsewhere.
u := User{Name: "Ada", Age: 36}
t := reflect.TypeOf(u)
for j := 0; j < t.NumField(); j++ {
f := t.Field(j)
fmt.Printf("field %s json=%q\n", f.Name, f.Tag.Get("json"))
}
}Expected output:
assert string: hello true
assert int: 0 false
int: 42
string len: 2
bool not: false
unknown: float64
field Name json="name"
field Age json="age"
What to notice: The comma-ok form never panics; a type switch is clearer than a chain of assertions. Reflection can read struct tags but costs more and loses static checking.
Try next: Replace the default branch with a generic PrintSlice[T any] for slice values, or make a failing assertion i.(int) without ok and observe the panic.