Go Data Types
Go's type system is one of its most powerful features, providing a rich set of built-in types that cover virtually all programming needs. This comprehensive guide will explore every aspect of Go's type system, from basic primitive types to complex composite types, helping you understand when and how to use each type effectively in your Go programs.
Understanding Go's Type System
What Makes Go's Type System Special?
Go's type system is designed with several key principles in mind:
Static Typing
Go is statically typed, meaning that variable types are checked at compile time. This prevents many runtime errors and makes programs more predictable and maintainable.
Type Safety
Go's type system prevents unsafe operations and type-related bugs through compile-time checks, ensuring that operations are performed only on compatible types.
Zero Values
Every type in Go has a well-defined zero value, which is automatically assigned when variables are declared without initialization.
Type Inference
Go can automatically infer types in many cases, reducing verbosity while maintaining type safety.
Categories of Go Types
Go types can be categorized into several groups:
Basic Types (Primitive Types)
- Integers: Various sizes of signed and unsigned integers
- Floating-point numbers: IEEE 754 floating-point representations
- Booleans: True/false values
- Strings: Unicode text strings
- Runes: Unicode code points
Composite Types
- Arrays: Fixed-size sequences of elements
- Slices: Dynamic arrays
- Maps: Key-value pairs
- Structs: Custom data structures
- Interfaces: Method signatures
- Channels: Communication mechanisms
Reference Types
- Pointers: Memory addresses
- Functions: Function values
- Interfaces: Interface values
Integer Types
Go provides multiple integer types to accommodate different ranges and memory requirements.
Signed Integer Types
package main
import (
"fmt"
"math"
)
func main() {
// Different signed integer types
var (
int8Var int8 = 127 // 8-bit signed integer
int16Var int16 = 32767 // 16-bit signed integer
int32Var int32 = 2147483647 // 32-bit signed integer
int64Var int64 = 9223372036854775807 // 64-bit signed integer
intVar int = 9223372036854775807 // Platform-dependent (32 or 64 bit)
)
fmt.Printf("int8: %d (range: %d to %d)\n", int8Var, math.MinInt8, math.MaxInt8)
fmt.Printf("int16: %d (range: %d to %d)\n", int16Var, math.MinInt16, math.MaxInt16)
fmt.Printf("int32: %d (range: %d to %d)\n", int32Var, math.MinInt32, math.MaxInt32)
fmt.Printf("int64: %d (range: %d to %d)\n", int64Var, math.MinInt64, math.MaxInt64)
fmt.Printf("int: %d (platform-dependent)\n", intVar)
}
Unsigned Integer Types
package main
import (
"fmt"
"math"
)
func main() {
// Different unsigned integer types
var (
uint8Var uint8 = 255 // 8-bit unsigned integer (byte)
uint16Var uint16 = 65535 // 16-bit unsigned integer
uint32Var uint32 = 4294967295 // 32-bit unsigned integer
uint64Var uint64 = 18446744073709551615 // 64-bit unsigned integer
uintVar uint = 18446744073709551615 // Platform-dependent
byteVar byte = 255 // Alias for uint8
)
fmt.Printf("uint8: %d (range: 0 to %d)\n", uint8Var, math.MaxUint8)
fmt.Printf("uint16: %d (range: 0 to %d)\n", uint16Var, math.MaxUint16)
fmt.Printf("uint32: %d (range: 0 to %d)\n", uint32Var, math.MaxUint32)
fmt.Printf("uint64: %d (range: 0 to %d)\n", uint64Var, math.MaxUint64)
fmt.Printf("uint: %d (platform-dependent)\n", uintVar)
fmt.Printf("byte: %d (alias for uint8)\n", byteVar)
}
Integer Literals and Representation
package main
import "fmt"
func main() {
// Different ways to represent integer literals
var (
decimal = 42 // Decimal (base 10)
binary = 0b101010 // Binary (base 2) - Go 1.13+
octal = 052 // Octal (base 8)
hexadecimal = 0x2A // Hexadecimal (base 16)
// Large numbers with underscores for readability
largeNumber = 1_000_000_000
creditCard = 1234_5678_9012_3456
)
fmt.Printf("Decimal: %d\n", decimal)
fmt.Printf("Binary: %b (decimal: %d)\n", binary, binary)
fmt.Printf("Octal: %o (decimal: %d)\n", octal, octal)
fmt.Printf("Hexadecimal: %x (decimal: %d)\n", hexadecimal, hexadecimal)
fmt.Printf("Large Number: %d\n", largeNumber)
fmt.Printf("Credit Card: %d\n", creditCard)
// All represent the same value: 42
fmt.Printf("All equal: %t\n", decimal == binary && binary == octal && octal == hexadecimal)
}
When to Use Different Integer Types
package main
import "fmt"
func main() {
// Use appropriate integer types based on requirements
// For counters and small ranges
var userCount uint16 = 1000 // Up to 65,535 users
// For ages (realistic range)
var age uint8 = 25 // Ages 0-255
// For IDs and large numbers
var userID uint64 = 123456789012345
// For file sizes
var fileSize int64 = 1024 * 1024 * 1024 // 1GB
// For array indices
var index int = 42 // Platform-optimized
// For byte manipulation
var flags byte = 0b10101010 // 8 bits of flags
fmt.Printf("User Count: %d\n", userCount)
fmt.Printf("Age: %d\n", age)
fmt.Printf("User ID: %d\n", userID)
fmt.Printf("File Size: %d bytes\n", fileSize)
fmt.Printf("Index: %d\n", index)
fmt.Printf("Flags: %08b\n", flags)
}
Floating-Point Types
Go provides two floating-point types following IEEE 754 standard.
Float32 and Float64
package main
import (
"fmt"
"math"
)
func main() {
// Floating-point types
var (
float32Var float32 = 3.14159
float64Var float64 = 3.141592653589793
pi float64 = math.Pi
e float64 = math.E
)
fmt.Printf("float32: %.5f\n", float32Var)
fmt.Printf("float64: %.15f\n", float64Var)
fmt.Printf("Pi: %.15f\n", pi)
fmt.Printf("E: %.15f\n", e)
// Precision comparison
fmt.Printf("float32 precision: %.20f\n", float32Var)
fmt.Printf("float64 precision: %.20f\n", float64Var)
}
Floating-Point Literals
package main
import "fmt"
func main() {
// Different ways to write floating-point literals
var (
decimalFloat = 3.14 // Standard decimal
scientific = 6.022e23 // Scientific notation
negativeExp = 1.67e-27 // Negative exponent
largeNumber = 1.23e+10 // Explicit positive exponent
zeroPoint = 0.0 // Zero
onePoint = 1.0 // One
)
fmt.Printf("Decimal: %f\n", decimalFloat)
fmt.Printf("Scientific: %e\n", scientific)
fmt.Printf("Negative Exponent: %e\n", negativeExp)
fmt.Printf("Large Number: %e\n", largeNumber)
fmt.Printf("Zero: %f\n", zeroPoint)
fmt.Printf("One: %f\n", onePoint)
}
Special Floating-Point Values
package main
import (
"fmt"
"math"
)
func main() {
// Special floating-point values
var (
positiveInf = math.Inf(1) // Positive infinity
negativeInf = math.Inf(-1) // Negative infinity
nan = math.NaN() // Not a Number
)
fmt.Printf("Positive Infinity: %f\n", positiveInf)
fmt.Printf("Negative Infinity: %f\n", negativeInf)
fmt.Printf("NaN: %f\n", nan)
// Check for special values
fmt.Printf("Is Inf: %t\n", math.IsInf(positiveInf, 1))
fmt.Printf("Is NaN: %t\n", math.IsNaN(nan))
// Operations with special values
fmt.Printf("Inf + 1: %f\n", positiveInf + 1)
fmt.Printf("NaN + 1: %f\n", nan + 1)
}
Boolean Type
Go has a single boolean type with two possible values.
Boolean Values and Operations
package main
import "fmt"
func main() {
// Boolean type
var (
isTrue bool = true
isFalse bool = false
isActive bool = true
isEnabled bool = false
)
fmt.Printf("isTrue: %t\n", isTrue)
fmt.Printf("isFalse: %t\n", isFalse)
fmt.Printf("isActive: %t\n", isActive)
fmt.Printf("isEnabled: %t\n", isEnabled)
// Boolean operations
fmt.Printf("AND (true && false): %t\n", isTrue && isFalse)
fmt.Printf("OR (true || false): %t\n", isTrue || isFalse)
fmt.Printf("NOT (!true): %t\n", !isTrue)
// Complex boolean expressions
result := (isActive && !isEnabled) || (isTrue && isFalse)
fmt.Printf("Complex expression: %t\n", result)
}
Boolean in Control Flow
package main
import "fmt"
func main() {
// Boolean values in control structures
var (
userLoggedIn = true
hasPermission = false
isAdmin = true
debugMode = false
)
// Conditional execution
if userLoggedIn {
fmt.Println("User is logged in")
}
// Complex conditions
if userLoggedIn && hasPermission {
fmt.Println("User can access resource")
} else if userLoggedIn && isAdmin {
fmt.Println("Admin override access")
} else {
fmt.Println("Access denied")
}
// Boolean variables for control flow
canEdit := userLoggedIn && (hasPermission || isAdmin)
if canEdit {
fmt.Println("User can edit content")
}
// Debug information
if debugMode {
fmt.Printf("Debug info: loggedIn=%t, permission=%t, admin=%t\n",
userLoggedIn, hasPermission, isAdmin)
}
}
String Type
Strings in Go are sequences of bytes representing UTF-8 encoded text.
String Basics
package main
import "fmt"
func main() {
// String literals
var (
singleLine = "Hello, World!"
multiLine = `This is a
multi-line string
that preserves formatting`
emptyString = ""
unicodeString = "Hello, 世界!" // UTF-8 encoded
)
fmt.Printf("Single line: %s\n", singleLine)
fmt.Printf("Multi-line:\n%s\n", multiLine)
fmt.Printf("Empty string: '%s'\n", emptyString)
fmt.Printf("Unicode string: %s\n", unicodeString)
// String length (in bytes, not characters)
fmt.Printf("Length of 'Hello': %d bytes\n", len("Hello"))
fmt.Printf("Length of '世界': %d bytes\n", len("世界")) // 6 bytes for 2 characters
}
String Operations
package main
import (
"fmt"
"strings"
)
func main() {
// String concatenation
firstName := "John"
lastName := "Doe"
fullName := firstName + " " + lastName
fmt.Printf("Full name: %s\n", fullName)
// String comparison
str1 := "apple"
str2 := "banana"
str3 := "apple"
fmt.Printf("'%s' == '%s': %t\n", str1, str2, str1 == str2)
fmt.Printf("'%s' == '%s': %t\n", str1, str3, str1 == str3)
fmt.Printf("'%s' < '%s': %t\n", str1, str2, str1 < str2)
// String indexing (returns bytes, not characters)
text := "Hello, 世界!"
fmt.Printf("First byte: %c\n", text[0])
fmt.Printf("Length: %d bytes\n", len(text))
// String slicing
fmt.Printf("First 5 characters: %s\n", text[:5])
fmt.Printf("Last 3 characters: %s\n", text[len(text)-3:])
// Using strings package for safe operations
fmt.Printf("Contains 'World': %t\n", strings.Contains(text, "World"))
fmt.Printf("Contains '世界': %t\n", strings.Contains(text, "世界"))
fmt.Printf("To Upper: %s\n", strings.ToUpper(text))
}
Raw String Literals
package main
import "fmt"
func main() {
// Raw string literals (backticks)
var (
jsonData = `{
"name": "John Doe",
"age": 30,
"city": "New York"
}`
sqlQuery = `SELECT name, age, city
FROM users
WHERE age > 25
ORDER BY name`
htmlTemplate = `<html>
<head><title>Welcome</title></head>
<body>
<h1>Hello, World!</h1>
<p>This is a test page.</p>
</body>
</html>`
)
fmt.Println("JSON Data:")
fmt.Println(jsonData)
fmt.Println("\nSQL Query:")
fmt.Println(sqlQuery)
fmt.Println("\nHTML Template:")
fmt.Println(htmlTemplate)
// Raw strings preserve all characters including newlines and quotes
var path = `C:\Users\John\Documents\file.txt`
fmt.Printf("Windows path: %s\n", path)
}
Rune Type
A rune in Go represents a Unicode code point and is an alias for int32
.
Understanding Runes
package main
import (
"fmt"
"unicode"
)
func main() {
// Rune literals
var (
letterA rune = 'A'
digit5 rune = '5'
emoji rune = '😀'
chinese rune = '中'
newline rune = '\n'
tab rune = '\t'
)
fmt.Printf("Letter A: %c (code point: %d)\n", letterA, letterA)
fmt.Printf("Digit 5: %c (code point: %d)\n", digit5, digit5)
fmt.Printf("Emoji: %c (code point: %d)\n", emoji, emoji)
fmt.Printf("Chinese: %c (code point: %d)\n", chinese, chinese)
fmt.Printf("Newline: %q (code point: %d)\n", newline, newline)
fmt.Printf("Tab: %q (code point: %d)\n", tab, tab)
// Rune operations
fmt.Printf("Is letter: %t\n", unicode.IsLetter(letterA))
fmt.Printf("Is digit: %t\n", unicode.IsDigit(digit5))
fmt.Printf("Is space: %t\n", unicode.IsSpace(' '))
fmt.Printf("To lower: %c\n", unicode.ToLower(letterA))
}
Working with Unicode Strings
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
text := "Hello, 世界! 🌍"
// String length vs character count
fmt.Printf("String: %s\n", text)
fmt.Printf("Byte length: %d\n", len(text))
fmt.Printf("Character count: %d\n", utf8.RuneCountInString(text))
// Iterating over runes (characters)
fmt.Println("Characters:")
for i, r := range text {
fmt.Printf("Position %d: %c (code point: %d)\n", i, r, r)
}
// Converting string to rune slice
runes := []rune(text)
fmt.Printf("Rune slice length: %d\n", len(runes))
// Accessing individual runes
fmt.Printf("First character: %c\n", runes[0])
fmt.Printf("Last character: %c\n", runes[len(runes)-1])
}
Complex Number Types
Go supports complex numbers with two types: complex64
and complex128
.
Complex Numbers
package main
import (
"fmt"
"math/cmplx"
)
func main() {
// Complex number creation
var (
c1 complex64 = 3 + 4i
c2 complex128 = 1.5 + 2.5i
c3 = complex(2, 3) // Type inference
)
fmt.Printf("Complex64: %v\n", c1)
fmt.Printf("Complex128: %v\n", c2)
fmt.Printf("Inferred: %v\n", c3)
// Complex number operations
sum := c1 + c2
product := c1 * c2
conjugate := cmplx.Conj(c2)
fmt.Printf("Sum: %v\n", sum)
fmt.Printf("Product: %v\n", product)
fmt.Printf("Conjugate: %v\n", conjugate)
// Complex number properties
fmt.Printf("Real part: %.2f\n", real(c2))
fmt.Printf("Imaginary part: %.2f\n", imag(c2))
fmt.Printf("Magnitude: %.2f\n", cmplx.Abs(c2))
fmt.Printf("Phase: %.2f radians\n", cmplx.Phase(c2))
}
Type Conversion
Go requires explicit type conversion between different types.
Numeric Type Conversions
package main
import "fmt"
func main() {
// Integer conversions
var (
int8Val int8 = 100
int16Val int16 = 200
int32Val int32 = 300
int64Val int64 = 400
)
// Converting between integer types
var result16 int16 = int16(int8Val) + int16Val
var result32 int32 = int32(result16) + int32Val
var result64 int64 = int64(result32) + int64Val
fmt.Printf("Result 16: %d\n", result16)
fmt.Printf("Result 32: %d\n", result32)
fmt.Printf("Result 64: %d\n", result64)
// Float conversions
var (
float32Val float32 = 3.14159
float64Val float64 = 2.71828
intVal int = 42
)
// Converting between float and int
var floatResult float64 = float64(float32Val) + float64Val
var intFromFloat int = int(floatResult)
var floatFromInt float64 = float64(intVal)
fmt.Printf("Float result: %.5f\n", floatResult)
fmt.Printf("Int from float: %d\n", intFromFloat)
fmt.Printf("Float from int: %.1f\n", floatFromInt)
}
String and Rune Conversions
package main
import "fmt"
func main() {
// String to rune slice
str := "Hello, 世界!"
runes := []rune(str)
fmt.Printf("Original string: %s\n", str)
fmt.Printf("Rune slice: %v\n", runes)
// Rune slice back to string
newStr := string(runes)
fmt.Printf("Back to string: %s\n", newStr)
// Individual rune to string
r := 'A'
charStr := string(r)
fmt.Printf("Rune to string: %s\n", charStr)
// Byte slice to string
bytes := []byte{'H', 'e', 'l', 'l', 'o'}
byteStr := string(bytes)
fmt.Printf("Byte slice to string: %s\n", byteStr)
// String to byte slice
byteSlice := []byte(str)
fmt.Printf("String to byte slice: %v\n", byteSlice)
}
Type Aliases and Type Definitions
Type Aliases
package main
import "fmt"
// Type alias (introduced in Go 1.9)
type UserID = int
type ProductID = int
func main() {
var userID UserID = 123
var productID ProductID = 456
fmt.Printf("User ID: %d\n", userID)
fmt.Printf("Product ID: %d\n", productID)
// Type aliases are interchangeable with their underlying type
var regularInt int = userID // No conversion needed
fmt.Printf("Regular int: %d\n", regularInt)
}
Type Definitions
package main
import "fmt"
// Type definition (creates a new type)
type Celsius float64
type Fahrenheit float64
// Methods for the new type
func (c Celsius) String() string {
return fmt.Sprintf("%.1f°C", c)
}
func (f Fahrenheit) String() string {
return fmt.Sprintf("%.1f°F", f)
}
// Conversion methods
func (c Celsius) ToFahrenheit() Fahrenheit {
return Fahrenheit(c*9/5 + 32)
}
func (f Fahrenheit) ToCelsius() Celsius {
return Celsius((f - 32) * 5 / 9)
}
func main() {
var tempC Celsius = 25.0
var tempF Fahrenheit = 77.0
fmt.Printf("Temperature in Celsius: %s\n", tempC)
fmt.Printf("Temperature in Fahrenheit: %s\n", tempF)
// Convert between types
convertedF := tempC.ToFahrenheit()
convertedC := tempF.ToCelsius()
fmt.Printf("25°C = %s\n", convertedF)
fmt.Printf("77°F = %s\n", convertedC)
// Type definitions require explicit conversion
// var mix = tempC + tempF // Error: cannot mix types
var mix = Celsius(tempC + tempF.ToCelsius())
fmt.Printf("Mixed calculation: %s\n", mix)
}
Best Practices for Data Types
Choosing the Right Type
package main
import (
"fmt"
"time"
)
func main() {
// Use appropriate types for different purposes
// For counters and small ranges
var pageViews uint32 = 1000000
// For ages and small positive numbers
var userAge uint8 = 25
// For monetary calculations (use integer cents to avoid floating-point errors)
var priceInCents int64 = 1999 // $19.99
// For scientific calculations
var temperature float64 = 23.5
// For flags and boolean states
var isActive bool = true
// For text data
var userName string = "John Doe"
// For single characters
var grade rune = 'A'
// For timestamps (Unix timestamp)
var createdAt int64 = time.Now().Unix()
fmt.Printf("Page Views: %d\n", pageViews)
fmt.Printf("User Age: %d\n", userAge)
fmt.Printf("Price: $%.2f\n", float64(priceInCents)/100)
fmt.Printf("Temperature: %.1f°C\n", temperature)
fmt.Printf("Active: %t\n", isActive)
fmt.Printf("User: %s\n", userName)
fmt.Printf("Grade: %c\n", grade)
fmt.Printf("Created: %d\n", createdAt)
}
Type Safety and Error Prevention
package main
import "fmt"
// Define custom types to prevent mixing different kinds of IDs
type UserID int
type ProductID int
func processUser(userID UserID) {
fmt.Printf("Processing user: %d\n", userID)
}
func processProduct(productID ProductID) {
fmt.Printf("Processing product: %d\n", productID)
}
func main() {
userID := UserID(123)
productID := ProductID(456)
// This works
processUser(userID)
processProduct(productID)
// This would cause a compile error (uncomment to see error)
// processUser(productID) // Error: cannot use ProductID as UserID
// Explicit conversion required
processUser(UserID(productID)) // Works but requires explicit conversion
}
What You've Learned
Congratulations! You now have a comprehensive understanding of Go's data types:
Integer Types
- Signed and unsigned integers of various sizes
- When to use each integer type
- Integer literals in different bases
- Platform-dependent
int
anduint
types
Floating-Point Types
float32
andfloat64
precision differences- Floating-point literals and scientific notation
- Special values (infinity, NaN)
- IEEE 754 standard compliance
Boolean Type
- Single boolean type with true/false values
- Boolean operations and expressions
- Usage in control flow structures
String Type
- UTF-8 encoded strings
- String literals and raw strings
- String operations and indexing
- Unicode support
Rune Type
- Unicode code points
- Character-level string processing
- Rune literals and operations
Complex Numbers
complex64
andcomplex128
types- Complex number operations
- Mathematical functions for complex numbers
Type System Features
- Type conversion requirements
- Type aliases vs. type definitions
- Type safety and error prevention
- Best practices for type selection
Next Steps
You now have a solid foundation in Go's type system. In the next section, we'll explore Go's operators, including arithmetic, comparison, logical, and bitwise operators, and learn how to write complex expressions effectively.
Understanding Go's data types is crucial for writing efficient and correct programs. These types form the foundation for all the more advanced data structures and operations we'll cover in the coming chapters.
Ready to learn about Go's operators? Let's explore arithmetic, comparison, logical, and bitwise operators in the next section!