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
- Empty Interface:
interface{}can hold any type - Comma-OK Idiom: Always use
val, ok := x.(Type)for safety - Type Switches: Elegant way to handle multiple types
- Type Information: Use
%Tformat verb to print type - Interface Satisfaction: Any type satisfying interface methods can be used
Related Topics
- Interfaces - Main tutorial on interfaces
- Type System Advanced - Advanced type patterns
- Reflection - Runtime type inspection