Go Type Conversion
Type conversion is a fundamental aspect of Go programming that allows you to transform values from one type to another. Unlike some languages that perform automatic type coercion, Go requires explicit type conversion for most type changes, making programs more predictable and safer. This comprehensive guide will teach you all aspects of Go's type conversion system, from basic casting to advanced type assertions.
Understanding Type Conversion in Go
What Is Type Conversion?
Type conversion (also called type casting) is the process of changing a value from one data type to another. In Go, this is typically an explicit operation that tells the compiler how to interpret and transform data.
Go's Philosophy on Type Conversion
Explicit Over Implicit
Go favors explicit type conversion over implicit coercion. This approach:
- Prevents unexpected behavior by making type changes visible
- Improves code readability by clearly showing intent
- Catches errors at compile time rather than runtime
- Makes programs more maintainable by reducing hidden assumptions
Type Safety First
Go's type system is designed to prevent common programming errors:
- No automatic conversions that might lose data
- Explicit conversion required for most type changes
- Compile-time checking for type compatibility
- Clear error messages when conversions are invalid
Categories of Type Conversion in Go
Explicit Type Conversion
Direct conversion between compatible types using type casting syntax.
Type Assertions
Runtime type checking and conversion for interface types.
Type Switches
Pattern matching for interface types with multiple possible types.
String Conversions
Special conversion rules for strings and byte slices.
Explicit Type Conversion
Numeric Type Conversions
package main
import "fmt"
func main() {
// Converting between integer types
var (
int8Val int8 = 100
int16Val int16 = 200
int32Val int32 = 300
int64Val int64 = 400
)
fmt.Printf("Original values:\n")
fmt.Printf("int8: %d\n", int8Val)
fmt.Printf("int16: %d\n", int16Val)
fmt.Printf("int32: %d\n", int32Val)
fmt.Printf("int64: %d\n", int64Val)
fmt.Println()
// Converting to larger types (safe)
var (
result16 int16 = int16(int8Val) + int16Val
result32 int32 = int32(result16) + int32Val
result64 int64 = int64(result32) + int64Val
)
fmt.Printf("Conversions to larger types:\n")
fmt.Printf("int16 result: %d\n", result16)
fmt.Printf("int32 result: %d\n", result32)
fmt.Printf("int64 result: %d\n", result64)
fmt.Println()
// Converting to smaller types (may cause overflow)
var largeInt int64 = 1000
var smallInt8 int8 = int8(largeInt) // May overflow
fmt.Printf("Converting large value %d to int8: %d\n", largeInt, smallInt8)
// Safe conversion with overflow check
if largeInt > 127 || largeInt < -128 {
fmt.Println("Warning: Potential overflow in conversion")
}
}
Integer to Floating-Point Conversion
package main
import "fmt"
func main() {
// Converting integers to floating-point numbers
var (
intVal int = 42
int32Val int32 = 100
int64Val int64 = 1000
)
// Convert to float32
var float32Val float32 = float32(intVal)
var float32Val2 float32 = float32(int32Val)
var float32Val3 float32 = float32(int64Val)
fmt.Printf("Integer to float32 conversions:\n")
fmt.Printf("%d -> %.2f\n", intVal, float32Val)
fmt.Printf("%d -> %.2f\n", int32Val, float32Val2)
fmt.Printf("%d -> %.2f\n", int64Val, float32Val3)
fmt.Println()
// Convert to float64
var float64Val float64 = float64(intVal)
var float64Val2 float64 = float64(int32Val)
var float64Val3 float64 = float64(int64Val)
fmt.Printf("Integer to float64 conversions:\n")
fmt.Printf("%d -> %.2f\n", intVal, float64Val)
fmt.Printf("%d -> %.2f\n", int32Val, float64Val2)
fmt.Printf("%d -> %.2f\n", int64Val, float64Val3)
fmt.Println()
// Precision demonstration
var preciseInt int64 = 9223372036854775807 // Max int64
var preciseFloat float64 = float64(preciseInt)
fmt.Printf("Precision test:\n")
fmt.Printf("Original int64: %d\n", preciseInt)
fmt.Printf("Converted float64: %.0f\n", preciseFloat)
fmt.Printf("Back to int64: %d\n", int64(preciseFloat))
}
Floating-Point to Integer Conversion
package main
import (
"fmt"
"math"
)
func main() {
// Converting floating-point numbers to integers
var (
float32Val float32 = 3.14159
float64Val float64 = 2.71828
negativeFloat float64 = -5.7
largeFloat float64 = 123456789.987654321
)
fmt.Printf("Floating-point to integer conversions:\n")
fmt.Printf("float32 %.5f -> int: %d\n", float32Val, int(float32Val))
fmt.Printf("float64 %.5f -> int: %d\n", float64Val, int(float64Val))
fmt.Printf("negative %.1f -> int: %d\n", negativeFloat, int(negativeFloat))
fmt.Printf("large %.3f -> int: %d\n", largeFloat, int(largeFloat))
fmt.Println()
// Truncation behavior
var (
positiveFloat float64 = 3.9
negativeFloat2 float64 = -3.9
)
fmt.Printf("Truncation behavior:\n")
fmt.Printf("%.1f -> int: %d (truncated towards zero)\n", positiveFloat, int(positiveFloat))
fmt.Printf("%.1f -> int: %d (truncated towards zero)\n", negativeFloat2, int(negativeFloat2))
fmt.Println()
// Using math functions for different rounding behaviors
fmt.Printf("Different rounding methods:\n")
fmt.Printf("Original: %.1f\n", positiveFloat)
fmt.Printf("Truncated (int): %d\n", int(positiveFloat))
fmt.Printf("Rounded (math.Round): %.0f\n", math.Round(positiveFloat))
fmt.Printf("Ceiling (math.Ceil): %.0f\n", math.Ceil(positiveFloat))
fmt.Printf("Floor (math.Floor): %.0f\n", math.Floor(positiveFloat))
}
String and Character Conversions
package main
import "fmt"
func main() {
// Converting between strings and other types
// String to byte slice
str := "Hello, 世界!"
byteSlice := []byte(str)
fmt.Printf("String: %s\n", str)
fmt.Printf("Byte slice: %v\n", byteSlice)
fmt.Printf("Byte slice as string: %s\n", string(byteSlice))
fmt.Println()
// String to rune slice
runeSlice := []rune(str)
fmt.Printf("Rune slice: %v\n", runeSlice)
fmt.Printf("Rune slice as string: %s\n", string(runeSlice))
fmt.Println()
// Individual characters
for i, r := range str {
fmt.Printf("Position %d: %c (rune: %d, byte: %d)\n", i, r, r, byte(r))
}
fmt.Println()
// Converting numbers to strings
var (
intNum int = 42
floatNum float64 = 3.14159
boolVal bool = true
)
// Note: Go doesn't have direct number-to-string conversion
// You need to use fmt.Sprintf or strconv package
fmt.Printf("Using fmt.Sprintf:\n")
fmt.Printf("int to string: %s\n", fmt.Sprintf("%d", intNum))
fmt.Printf("float to string: %s\n", fmt.Sprintf("%.2f", floatNum))
fmt.Printf("bool to string: %s\n", fmt.Sprintf("%t", boolVal))
}
Complex Number Conversions
package main
import "fmt"
func main() {
// Converting between complex number types
var (
complex64Val complex64 = 3 + 4i
complex128Val complex128 = 1.5 + 2.5i
)
fmt.Printf("Complex number conversions:\n")
fmt.Printf("complex64: %v\n", complex64Val)
fmt.Printf("complex128: %v\n", complex128Val)
// Converting between complex64 and complex128
var converted64 complex64 = complex64(complex128Val)
var converted128 complex128 = complex128(complex64Val)
fmt.Printf("complex128 to complex64: %v\n", converted64)
fmt.Printf("complex64 to complex128: %v\n", converted128)
fmt.Println()
// Converting from real numbers to complex
var (
realFloat float64 = 5.0
imagFloat float64 = 3.0
)
var complexFromReals complex128 = complex(realFloat, imagFloat)
fmt.Printf("From reals: %.1f + %.1fi = %v\n", realFloat, imagFloat, complexFromReals)
// Extracting real and imaginary parts
realPart := real(complexFromReals)
imagPart := imag(complexFromReals)
fmt.Printf("Real part: %.1f\n", realPart)
fmt.Printf("Imaginary part: %.1f\n", imagPart)
}
Type Assertions
Basic Type Assertions
package main
import "fmt"
func main() {
// Type assertions work with interface types
var value interface{} = "Hello, World!"
// Type assertion syntax: value.(Type)
str, ok := value.(string)
if ok {
fmt.Printf("Value is a string: %s\n", str)
} else {
fmt.Printf("Value is not a string\n")
}
// Type assertion without ok check (may panic)
str2 := value.(string)
fmt.Printf("Direct assertion: %s\n", str2)
// This would panic if the assertion fails
// intVal := value.(int) // panic: interface conversion error
}
Safe Type Assertions
package main
import "fmt"
func main() {
// Safe type assertions using the "comma ok" idiom
var values []interface{} = []interface{}{
"Hello",
42,
3.14,
true,
[]int{1, 2, 3},
}
fmt.Println("Safe type assertions:")
for i, value := range values {
fmt.Printf("Value %d: ", i)
// Try different type assertions
if str, ok := value.(string); ok {
fmt.Printf("String: %s\n", str)
} else if intVal, ok := value.(int); ok {
fmt.Printf("Integer: %d\n", intVal)
} else if floatVal, ok := value.(float64); ok {
fmt.Printf("Float: %.2f\n", floatVal)
} else if boolVal, ok := value.(bool); ok {
fmt.Printf("Boolean: %t\n", boolVal)
} else if slice, ok := value.([]int); ok {
fmt.Printf("Slice: %v\n", slice)
} else {
fmt.Printf("Unknown type: %T\n", value)
}
}
}
Type Assertions with Custom Types
package main
import "fmt"
// Custom types for demonstration
type UserID int
type ProductID int
type Status string
func processValue(value interface{}) {
fmt.Printf("Processing value: %v (type: %T)\n", value, value)
// Type assertion for custom types
switch v := value.(type) {
case UserID:
fmt.Printf(" User ID: %d\n", v)
case ProductID:
fmt.Printf(" Product ID: %d\n", v)
case Status:
fmt.Printf(" Status: %s\n", v)
case int:
fmt.Printf(" Generic integer: %d\n", v)
case string:
fmt.Printf(" Generic string: %s\n", v)
default:
fmt.Printf(" Unknown type: %T\n", v)
}
}
func main() {
// Test with different types
processValue(UserID(123))
processValue(ProductID(456))
processValue(Status("active"))
processValue(42)
processValue("hello")
processValue(3.14)
}
Type Switches
Basic Type Switches
package main
import "fmt"
func analyzeValue(value interface{}) {
fmt.Printf("Analyzing value: %v\n", value)
// Type switch syntax
switch v := value.(type) {
case int:
fmt.Printf(" Integer: %d\n", v)
fmt.Printf(" Square: %d\n", v*v)
case float64:
fmt.Printf(" Float: %.2f\n", v)
fmt.Printf(" Square: %.2f\n", v*v)
case string:
fmt.Printf(" String: %s\n", v)
fmt.Printf(" Length: %d\n", len(v))
case bool:
fmt.Printf(" Boolean: %t\n", v)
fmt.Printf(" Negation: %t\n", !v)
case []int:
fmt.Printf(" Integer slice: %v\n", v)
fmt.Printf(" Length: %d\n", len(v))
case nil:
fmt.Printf(" Nil value\n")
default:
fmt.Printf(" Unknown type: %T\n", v)
}
fmt.Println()
}
func main() {
// Test type switch with different values
analyzeValue(42)
analyzeValue(3.14159)
analyzeValue("Hello, World!")
analyzeValue(true)
analyzeValue([]int{1, 2, 3, 4, 5})
analyzeValue(nil)
analyzeValue(UserID(123)) // Custom type
}
// Custom type for demonstration
type UserID int
Advanced Type Switch Patterns
package main
import "fmt"
// Custom types for demonstration
type Temperature float64
type Humidity float64
type Pressure float64
type WeatherData struct {
Temp Temperature
Humidity Humidity
Pressure Pressure
}
func processSensorData(data interface{}) {
fmt.Printf("Processing sensor data: %v\n", data)
switch v := data.(type) {
case Temperature:
fmt.Printf(" Temperature: %.1f°C\n", v)
if v > 30 {
fmt.Printf(" Warning: High temperature!\n")
}
case Humidity:
fmt.Printf(" Humidity: %.1f%%\n", v)
if v > 80 {
fmt.Printf(" Warning: High humidity!\n")
}
case Pressure:
fmt.Printf(" Pressure: %.1f hPa\n", v)
if v < 1000 {
fmt.Printf(" Warning: Low pressure!\n")
}
case WeatherData:
fmt.Printf(" Complete weather data:\n")
fmt.Printf(" Temperature: %.1f°C\n", v.Temp)
fmt.Printf(" Humidity: %.1f%%\n", v.Humidity)
fmt.Printf(" Pressure: %.1f hPa\n", v.Pressure)
case []interface{}:
fmt.Printf(" Multiple sensor readings:\n")
for i, reading := range v {
fmt.Printf(" Reading %d: ", i+1)
processSensorData(reading)
}
default:
fmt.Printf(" Unknown sensor data type: %T\n", v)
}
fmt.Println()
}
func main() {
// Test with different sensor data types
processSensorData(Temperature(25.5))
processSensorData(Humidity(65.0))
processSensorData(Pressure(1013.25))
weather := WeatherData{
Temp: Temperature(22.0),
Humidity: Humidity(70.0),
Pressure: Pressure(1015.0),
}
processSensorData(weather)
// Multiple readings
readings := []interface{}{
Temperature(24.0),
Humidity(68.0),
Pressure(1012.0),
}
processSensorData(readings)
}
String Conversion Utilities
Using the strconv Package
package main
import (
"fmt"
"strconv"
)
func main() {
// Converting strings to numbers
fmt.Println("String to number conversions:")
// String to integer
strInt := "42"
intVal, err := strconv.Atoi(strInt)
if err != nil {
fmt.Printf("Error converting %s to int: %v\n", strInt, err)
} else {
fmt.Printf("String '%s' -> int: %d\n", strInt, intVal)
}
// String to int64
strInt64 := "9223372036854775807"
int64Val, err := strconv.ParseInt(strInt64, 10, 64)
if err != nil {
fmt.Printf("Error converting %s to int64: %v\n", strInt64, err)
} else {
fmt.Printf("String '%s' -> int64: %d\n", strInt64, int64Val)
}
// String to float64
strFloat := "3.14159"
floatVal, err := strconv.ParseFloat(strFloat, 64)
if err != nil {
fmt.Printf("Error converting %s to float64: %v\n", strFloat, err)
} else {
fmt.Printf("String '%s' -> float64: %.5f\n", strFloat, floatVal)
}
// String to boolean
strBool := "true"
boolVal, err := strconv.ParseBool(strBool)
if err != nil {
fmt.Printf("Error converting %s to bool: %v\n", strBool, err)
} else {
fmt.Printf("String '%s' -> bool: %t\n", strBool, boolVal)
}
fmt.Println()
// Converting numbers to strings
fmt.Println("Number to string conversions:")
// Integer to string
intToStr := strconv.Itoa(42)
fmt.Printf("int 42 -> string: '%s'\n", intToStr)
// Int64 to string
int64ToStr := strconv.FormatInt(9223372036854775807, 10)
fmt.Printf("int64 max -> string: '%s'\n", int64ToStr)
// Float to string
floatToStr := strconv.FormatFloat(3.14159, 'f', 2, 64)
fmt.Printf("float64 3.14159 -> string: '%s'\n", floatToStr)
// Boolean to string
boolToStr := strconv.FormatBool(true)
fmt.Printf("bool true -> string: '%s'\n", boolToStr)
}
Advanced String Conversions
package main
import (
"fmt"
"strconv"
)
func main() {
// Advanced string conversion options
fmt.Println("Advanced string conversions:")
// Different base conversions
num := 255
fmt.Printf("Number %d in different bases:\n", num)
fmt.Printf(" Binary (base 2): %s\n", strconv.FormatInt(int64(num), 2))
fmt.Printf(" Octal (base 8): %s\n", strconv.FormatInt(int64(num), 8))
fmt.Printf(" Decimal (base 10): %s\n", strconv.FormatInt(int64(num), 10))
fmt.Printf(" Hexadecimal (base 16): %s\n", strconv.FormatInt(int64(num), 16))
fmt.Println()
// Parsing with different bases
fmt.Println("Parsing strings in different bases:")
binaryStr := "11111111"
if val, err := strconv.ParseInt(binaryStr, 2, 64); err == nil {
fmt.Printf("Binary '%s' -> decimal: %d\n", binaryStr, val)
}
hexStr := "FF"
if val, err := strconv.ParseInt(hexStr, 16, 64); err == nil {
fmt.Printf("Hexadecimal '%s' -> decimal: %d\n", hexStr, val)
}
fmt.Println()
// Float formatting options
pi := 3.14159265359
fmt.Printf("Float formatting options for %.11f:\n", pi)
fmt.Printf(" Fixed precision (2): %s\n", strconv.FormatFloat(pi, 'f', 2, 64))
fmt.Printf(" Scientific notation: %s\n", strconv.FormatFloat(pi, 'e', 2, 64))
fmt.Printf(" Compact scientific: %s\n", strconv.FormatFloat(pi, 'g', -1, 64))
fmt.Printf(" Exponent notation: %s\n", strconv.FormatFloat(pi, 'E', 2, 64))
}
Best Practices for Type Conversion
Safe Conversion Patterns
package main
import (
"fmt"
"strconv"
)
// Safe conversion functions
func safeStringToInt(s string) (int, error) {
return strconv.Atoi(s)
}
func safeStringToFloat(s string) (float64, error) {
return strconv.ParseFloat(s, 64)
}
func safeIntToString(i int) string {
return strconv.Itoa(i)
}
func safeFloatToString(f float64, precision int) string {
return strconv.FormatFloat(f, 'f', precision, 64)
}
func main() {
fmt.Println("Safe conversion patterns:")
// Safe string to number conversion
testStrings := []string{"42", "3.14", "not-a-number", "999999999999999999999"}
for _, str := range testStrings {
if intVal, err := safeStringToInt(str); err == nil {
fmt.Printf("'%s' -> int: %d\n", str, intVal)
} else {
fmt.Printf("'%s' -> int: ERROR (%v)\n", str, err)
}
}
fmt.Println()
// Safe number to string conversion
testNumbers := []int{42, 0, -123, 999999999}
for _, num := range testNumbers {
str := safeIntToString(num)
fmt.Printf("%d -> string: '%s'\n", num, str)
}
fmt.Println()
// Safe float conversion with precision control
testFloats := []float64{3.14159, 2.71828, 1.0, 0.0}
for _, f := range testFloats {
str := safeFloatToString(f, 2)
fmt.Printf("%.5f -> string: '%s'\n", f, str)
}
}
Error Handling in Type Conversion
package main
import (
"fmt"
"strconv"
)
func processUserInput(input string) {
fmt.Printf("Processing input: '%s'\n", input)
// Try different type conversions
if intVal, err := strconv.Atoi(input); err == nil {
fmt.Printf(" Valid integer: %d\n", intVal)
return
}
if floatVal, err := strconv.ParseFloat(input, 64); err == nil {
fmt.Printf(" Valid float: %.2f\n", floatVal)
return
}
if boolVal, err := strconv.ParseBool(input); err == nil {
fmt.Printf(" Valid boolean: %t\n", boolVal)
return
}
// Check if it's a valid string
if len(input) > 0 {
fmt.Printf(" Valid string: '%s' (length: %d)\n", input, len(input))
return
}
fmt.Printf(" Invalid or empty input\n")
}
func main() {
fmt.Println("Error handling in type conversion:")
testInputs := []string{
"42",
"3.14",
"true",
"false",
"hello",
"",
"not-a-number",
"999999999999999999999",
}
for _, input := range testInputs {
processUserInput(input)
}
}
Performance Considerations
package main
import (
"fmt"
"strconv"
"time"
)
func benchmarkConversions() {
fmt.Println("Performance considerations for type conversions:")
// Benchmark string to int conversion
testString := "42"
iterations := 1000000
// Method 1: strconv.Atoi
start := time.Now()
for i := 0; i < iterations; i++ {
strconv.Atoi(testString)
}
atoiTime := time.Since(start)
// Method 2: strconv.ParseInt
start = time.Now()
for i := 0; i < iterations; i++ {
strconv.ParseInt(testString, 10, 64)
}
parseIntTime := time.Since(start)
fmt.Printf("strconv.Atoi: %v for %d iterations\n", atoiTime, iterations)
fmt.Printf("strconv.ParseInt: %v for %d iterations\n", parseIntTime, iterations)
fmt.Printf("Atoi is %.1fx faster\n", float64(parseIntTime)/float64(atoiTime))
fmt.Println()
// Benchmark int to string conversion
testInt := 42
// Method 1: strconv.Itoa
start = time.Now()
for i := 0; i < iterations; i++ {
strconv.Itoa(testInt)
}
itoaTime := time.Since(start)
// Method 2: strconv.FormatInt
start = time.Now()
for i := 0; i < iterations; i++ {
strconv.FormatInt(int64(testInt), 10)
}
formatIntTime := time.Since(start)
// Method 3: fmt.Sprintf
start = time.Now()
for i := 0; i < iterations; i++ {
fmt.Sprintf("%d", testInt)
}
sprintfTime := time.Since(start)
fmt.Printf("strconv.Itoa: %v\n", itoaTime)
fmt.Printf("strconv.FormatInt: %v\n", formatIntTime)
fmt.Printf("fmt.Sprintf: %v\n", sprintfTime)
fmt.Printf("Itoa is %.1fx faster than FormatInt\n", float64(formatIntTime)/float64(itoaTime))
fmt.Printf("Itoa is %.1fx faster than Sprintf\n", float64(sprintfTime)/float64(itoaTime))
}
func main() {
benchmarkConversions()
}
What You've Learned
Congratulations! You now have a comprehensive understanding of Go's type conversion system:
Explicit Type Conversion
- Converting between numeric types safely
- Handling potential overflow and precision loss
- Converting between strings and other types
- Complex number type conversions
Type Assertions
- Safe type assertions using the "comma ok" idiom
- Runtime type checking for interface types
- Handling assertion failures gracefully
- Type assertions with custom types
Type Switches
- Pattern matching with type switches
- Handling multiple possible types
- Advanced type switch patterns
- Combining type switches with custom types
String Conversion Utilities
- Using the strconv package effectively
- Converting between strings and numbers
- Handling different number bases
- Advanced string formatting options
Best Practices
- Safe conversion patterns and error handling
- Performance considerations for conversions
- Choosing the right conversion method
- Writing maintainable conversion code
Next Steps
You now have a solid foundation in Go's type system and conversion mechanisms. In the next chapter, we'll explore Go's control flow structures, including conditional statements, loops, and how to control the execution flow of your programs.
Understanding type conversion is crucial for writing robust Go programs that handle different data types safely and efficiently. These concepts form the foundation for all the more advanced programming techniques we'll cover in the coming chapters.
Ready to learn about control flow? Let's explore Go's conditional statements and loops in the next chapter!