Type Assertions

Exercise: Type Assertions

Difficulty - Beginner

📋 Prerequisites: This exercise assumes familiarity with Interfaces in Go for understanding interface concepts, and Error Handling for error return patterns.

Learning Objectives

  • Understand interface{} and empty interface
  • Master type assertions with the comma-ok idiom
  • Learn type switches for handling multiple types
  • Practice type conversion patterns
  • Understand interface satisfaction

Problem Statement

Create a types package that demonstrates type assertions and type switches for handling different data types.

Interface and Type Definitions

 1package types
 2
 3// Shape interface for geometric shapes
 4type Shape interface {
 5	Area() float64
 6}
 7
 8// Circle represents a circle
 9type Circle struct {
10	Radius float64
11}
12
13func Area() float64
14
15// Square represents a square
16type Square struct {
17	Side float64
18}
19
20func Area() float64
21
22// GetShapeInfo returns information about a shape
23func GetShapeInfo(s Shape) string
24
25// ProcessValue handles different types of values
26func ProcessValue(val interface{}) string
27
28// SumNumbers sums various numeric types
29func SumNumbers(values ...interface{})
30
31// ExtractStrings extracts all strings from a slice of interface{}
32func ExtractStrings(values []interface{}) []string
33
34// TypeSwitch demonstrates type switch usage
35func TypeSwitch(val interface{}) string

Example Usage

 1package main
 2
 3import (
 4	"fmt"
 5	"types"
 6)
 7
 8func main() {
 9	// Shape interface usage
10	var shape types.Shape
11
12	circle := types.Circle{Radius: 5}
13	shape = circle
14	fmt.Println(types.GetShapeInfo(shape))
15
16	square := types.Square{Side: 4}
17	shape = square
18	fmt.Println(types.GetShapeInfo(shape))
19
20	// Process different value types
21	fmt.Println(types.ProcessValue(42))
22	fmt.Println(types.ProcessValue("hello"))
23	fmt.Println(types.ProcessValue(3.14))
24	fmt.Println(types.ProcessValue(true))
25
26	// Sum numbers of different types
27	sum, _ := types.SumNumbers(1, 2.5, 3, 4.5)
28	fmt.Printf("Sum: %.2f\n", sum)
29
30	// Extract strings
31	mixed := []interface{}{"hello", 42, "world", 3.14, "go"}
32	strings := types.ExtractStrings(mixed)
33	fmt.Println("Strings:", strings)
34
35	// Type switch demonstration
36	fmt.Println(types.TypeSwitch(100))
37	fmt.Println(types.TypeSwitch("test"))
38	fmt.Println(types.TypeSwitch([]int{1, 2, 3}))
39}

Solution

Click to see the complete solution
  1package types
  2
  3import (
  4	"fmt"
  5	"math"
  6)
  7
  8// Shape interface for geometric shapes
  9type Shape interface {
 10	Area() float64
 11}
 12
 13// Circle represents a circle
 14type Circle struct {
 15	Radius float64
 16}
 17
 18func Area() float64 {
 19	return math.Pi * c.Radius * c.Radius
 20}
 21
 22// Square represents a square
 23type Square struct {
 24	Side float64
 25}
 26
 27func Area() float64 {
 28	return s.Side * s.Side
 29}
 30
 31// GetShapeInfo returns information about a shape
 32func GetShapeInfo(s Shape) string {
 33	// Type assertion to get specific type information
 34	switch shape := s.(type) {
 35	case Circle:
 36		return fmt.Sprintf("Circle with radius %.2f, area: %.2f",
 37			shape.Radius, shape.Area())
 38	case Square:
 39		return fmt.Sprintf("Square with side %.2f, area: %.2f",
 40			shape.Side, shape.Area())
 41	default:
 42		return fmt.Sprintf("Unknown shape, area: %.2f", s.Area())
 43	}
 44}
 45
 46// ProcessValue handles different types of values
 47func ProcessValue(val interface{}) string {
 48	// Comma-ok idiom for safe type assertion
 49	if v, ok := val.(int); ok {
 50		return fmt.Sprintf("Integer: %d", v, v*2)
 51	}
 52
 53	if v, ok := val.(string); ok {
 54		return fmt.Sprintf("String: %q", v, len(v))
 55	}
 56
 57	if v, ok := val.(float64); ok {
 58		return fmt.Sprintf("Float: %.2f", v, v*v)
 59	}
 60
 61	if v, ok := val.(bool); ok {
 62		return fmt.Sprintf("Boolean: %t", v, !v)
 63	}
 64
 65	return fmt.Sprintf("Unknown type: %T", val)
 66}
 67
 68// SumNumbers sums various numeric types
 69func SumNumbers(values ...interface{}) {
 70	var sum float64
 71
 72	for i, val := range values {
 73		switch v := val.(type) {
 74		case int:
 75			sum += float64(v)
 76		case int64:
 77			sum += float64(v)
 78		case float32:
 79			sum += float64(v)
 80		case float64:
 81			sum += v
 82		default:
 83			return 0, fmt.Errorf("value at index %d is not a number: %T", i, val)
 84		}
 85	}
 86
 87	return sum, nil
 88}
 89
 90// ExtractStrings extracts all strings from a slice of interface{}
 91func ExtractStrings(values []interface{}) []string {
 92	var strings []string
 93
 94	for _, val := range values {
 95		if str, ok := val.(string); ok {
 96			strings = append(strings, str)
 97		}
 98	}
 99
100	return strings
101}
102
103// TypeSwitch demonstrates type switch usage
104func TypeSwitch(val interface{}) string {
105	switch v := val.(type) {
106	case int:
107		return fmt.Sprintf("int: %d", v)
108	case string:
109		return fmt.Sprintf("string: %q", v)
110	case bool:
111		return fmt.Sprintf("bool: %t", v)
112	case float64:
113		return fmt.Sprintf("float64: %.2f", v)
114	case []int:
115		return fmt.Sprintf("[]int with %d elements", len(v))
116	case nil:
117		return "nil value"
118	default:
119		return fmt.Sprintf("unknown type: %T", v)
120	}
121}

Key Takeaways

  1. Empty Interface: interface{} can hold any type
  2. Comma-OK Idiom: Always use val, ok := x.(Type) for safety
  3. Type Switches: Elegant way to handle multiple types
  4. Type Information: Use %T format verb to print type
  5. Interface Satisfaction: Any type satisfying interface methods can be used