Web Analytics

Type Switches & Common Interfaces

Intermediate ~30 min read

Type switches and assertions let you work with interface values dynamically. Go's standard library also provides essential interfaces that you'll use constantly. In this lesson, you'll master both concepts.

Type Assertions

Type assertions extract the concrete value from an interface:

Output
Click Run to execute your code
Type Assertion Syntax:
// Single return (panics if wrong type)
value := interfaceValue.(ConcreteType)

// Two returns (safe, doesn't panic)
value, ok := interfaceValue.(ConcreteType)
if ok {
    // Type assertion succeeded
}
Best Practice: Always use the two-value form of type assertion to avoid panics: value, ok := i.(Type)
Type Assertion and Type Switch in Go

Figure: Type Assertion and Type Switch - Safe vs unsafe type assertions and handling multiple types with type switch

Type Switches

Type switches let you handle different types in a clean way:

Output
Click Run to execute your code
Type Switch Syntax:
switch v := interfaceValue.(type) {
case Type1:
    // v is Type1
case Type2:
    // v is Type2
default:
    // v is the interface type
}

Type Switch Patterns

// Multiple types in one case
switch v := i.(type) {
case int, int64:
    fmt.Println("Integer:", v)
case string:
    fmt.Println("String:", v)
case nil:
    fmt.Println("Nil value")
default:
    fmt.Printf("Unknown type: %T\n", v)
}

Common Standard Library Interfaces

1. fmt.Stringer

The Stringer interface customizes how types are printed:

type Stringer interface {
    String() string
}

type Person struct {
    Name string
    Age  int
}

func (p Person) String() string {
    return fmt.Sprintf("%s (%d years old)", p.Name, p.Age)
}

func main() {
    p := Person{Name: "Alice", Age: 25}
    fmt.Println(p)  // Alice (25 years old)
}

2. io.Reader

Reader reads data into a byte slice:

type Reader interface {
    Read(p []byte) (n int, err error)
}

// Common implementations:
// - os.File
// - strings.Reader
// - bytes.Buffer
// - net.Conn

func readData(r io.Reader) {
    buf := make([]byte, 1024)
    n, err := r.Read(buf)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    fmt.Printf("Read %d bytes: %s\n", n, buf[:n])
}

3. io.Writer

Writer writes data from a byte slice:

type Writer interface {
    Write(p []byte) (n int, err error)
}

// Common implementations:
// - os.File
// - bytes.Buffer
// - net.Conn
// - http.ResponseWriter

func writeData(w io.Writer, data string) {
    n, err := w.Write([]byte(data))
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    fmt.Printf("Wrote %d bytes\n", n)
}

4. error Interface

The error interface is used throughout Go:

type error interface {
    Error() string
}

// Custom error type
type ValidationError struct {
    Field   string
    Message string
}

func (e ValidationError) Error() string {
    return fmt.Sprintf("%s: %s", e.Field, e.Message)
}

func validate(age int) error {
    if age < 0 {
        return ValidationError{
            Field:   "age",
            Message: "must be non-negative",
        }
    }
    return nil
}

Common Standard Library Interfaces

Interface Package Purpose
Stringer fmt Custom string representation
Reader io Read data
Writer io Write data
Closer io Close resources
error builtin Error handling
Handler http HTTP request handling

Practical Examples

JSON Marshaling with Interfaces

type Animal interface {
    Speak() string
}

type Dog struct {
    Name string
}

func (d Dog) Speak() string {
    return "Woof!"
}

type Cat struct {
    Name string
}

func (c Cat) Speak() string {
    return "Meow!"
}

func main() {
    animals := []Animal{
        Dog{Name: "Buddy"},
        Cat{Name: "Whiskers"},
    }
    
    for _, animal := range animals {
        // Type switch to get specific behavior
        switch a := animal.(type) {
        case Dog:
            fmt.Printf("%s says %s\n", a.Name, a.Speak())
        case Cat:
            fmt.Printf("%s says %s\n", a.Name, a.Speak())
        }
    }
}

Common Mistakes

1. Type assertion without checking

// โŒ Wrong - panics if wrong type
var i interface{} = "hello"
num := i.(int)  // panic!

// โœ… Correct - check before using
if num, ok := i.(int); ok {
    fmt.Println(num)
} else {
    fmt.Println("Not an int")
}

2. Forgetting nil case in type switch

// โŒ Incomplete - doesn't handle nil
func process(i interface{}) {
    switch v := i.(type) {
    case string:
        fmt.Println("String:", v)
    case int:
        fmt.Println("Int:", v)
    }
}

// โœ… Complete - handles nil
func process(i interface{}) {
    switch v := i.(type) {
    case nil:
        fmt.Println("Nil value")
    case string:
        fmt.Println("String:", v)
    case int:
        fmt.Println("Int:", v)
    default:
        fmt.Printf("Unknown: %T\n", v)
    }
}

3. Not implementing error interface correctly

// โŒ Wrong - pointer receiver
type MyError struct {
    msg string
}

func (e *MyError) Error() string {
    return e.msg
}

// This doesn't work as expected
err := MyError{msg: "oops"}
var e error = err  // Error: MyError doesn't implement error

// โœ… Correct - value receiver for error
func (e MyError) Error() string {
    return e.msg
}

err := MyError{msg: "oops"}
var e error = err  // OK

Exercise: Shape Calculator

Task: Create a shape calculator using interfaces and type switches.

Requirements:

  • Define a Shape interface with Area() method
  • Implement Circle and Rectangle
  • Add String() method to both (fmt.Stringer)
  • Use type switch to print specific details
Show Solution
package main

import (
    "fmt"
    "math"
)

// Shape interface
type Shape interface {
    Area() float64
}

// Circle implementation
type Circle struct {
    Radius float64
}

func (c Circle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}

func (c Circle) String() string {
    return fmt.Sprintf("Circle(radius=%.2f)", c.Radius)
}

// Rectangle implementation
type Rectangle struct {
    Width  float64
    Height float64
}

func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

func (r Rectangle) String() string {
    return fmt.Sprintf("Rectangle(%.2f x %.2f)", r.Width, r.Height)
}

// Triangle implementation (bonus)
type Triangle struct {
    Base   float64
    Height float64
}

func (t Triangle) Area() float64 {
    return 0.5 * t.Base * t.Height
}

func (t Triangle) String() string {
    return fmt.Sprintf("Triangle(base=%.2f, height=%.2f)", t.Base, t.Height)
}

// Describe shape using type switch
func describe(s Shape) {
    fmt.Printf("%s has area %.2f\n", s, s.Area())
    
    // Type switch for specific details
    switch shape := s.(type) {
    case Circle:
        fmt.Printf("  Circumference: %.2f\n", 2*math.Pi*shape.Radius)
    case Rectangle:
        fmt.Printf("  Perimeter: %.2f\n", 2*(shape.Width+shape.Height))
    case Triangle:
        fmt.Printf("  Base: %.2f, Height: %.2f\n", shape.Base, shape.Height)
    default:
        fmt.Printf("  Unknown shape type: %T\n", shape)
    }
}

func main() {
    shapes := []Shape{
        Circle{Radius: 5},
        Rectangle{Width: 4, Height: 6},
        Triangle{Base: 3, Height: 4},
    }
    
    fmt.Println("Shape Calculator")
    fmt.Println("================")
    
    for _, shape := range shapes {
        describe(shape)
        fmt.Println()
    }
    
    // Calculate total area
    totalArea := 0.0
    for _, shape := range shapes {
        totalArea += shape.Area()
    }
    fmt.Printf("Total area of all shapes: %.2f\n", totalArea)
}

Summary

  • Type assertions extract concrete values from interfaces
  • Use two-value form to avoid panics: v, ok := i.(Type)
  • Type switches handle multiple types cleanly
  • fmt.Stringer customizes string representation
  • io.Reader/Writer are fundamental for I/O operations
  • error interface is used for error handling
  • Small interfaces are more flexible and composable
  • Standard library interfaces enable powerful abstractions

What's Next?

Congratulations on completing the Pointers & Interfaces module! You now understand Go's memory model and polymorphism. Next, you'll learn about Error Handlingโ€”Go's philosophy on errors, custom errors, and best practices.