Data Types

Go has several basic types including numerics, strings, booleans, arrays, slices, and maps. Let's explore each type in detail.

1. Numeric Types

Integer Types

  1. Signed integers

    • int8: -128 to 127

    • int16: -32768 to 32767

    • int32: -2147483648 to 2147483647

    • int64: -9223372036854775808 to 9223372036854775807

    • int: Platform dependent (32 or 64 bit)

  2. Unsigned integers

    • uint8: 0 to 255 (byte is an alias for uint8)

    • uint16: 0 to 65535

    • uint32: 0 to 4294967295

    • uint64: 0 to 18446744073709551615

    • uint: Platform dependent (32 or 64 bit)

    • uintptr: Integer large enough to store any pointer address

Floating-Point Types

  • float32: IEEE-754 32-bit floating-point numbers

  • float64: IEEE-754 64-bit floating-point numbers (default for floating-point)

Complex Numbers

  • complex64: Complex numbers with float32 real and imaginary parts

  • complex128: Complex numbers with float64 real and imaginary parts (default for complex)

Floating-Point Formatting

f := 3.14159265359

// Basic formatting
fmt.Printf("Default: %f\n", f)         // Default: 3.141593
fmt.Printf("2 decimals: %.2f\n", f)    // 2 decimals: 3.14
fmt.Printf("Scientific: %e\n", f)      // Scientific: 3.141593e+00
fmt.Printf("Width 10: %10.2f\n", f)    // Width 10:      3.14
fmt.Printf("Padded: %010.2f\n", f)     // Padded: 0000003.14

// Using strconv
s := strconv.FormatFloat(f, 'f', 2, 64)  // "3.14"

Examples

package main

import "fmt"

func main() {
    // Integer examples
    var i int = 42
    var i8 int8 = 127
    var ui uint = 123
    
    // Floating point examples
    var f32 float32 = 3.14
    var f64 float64 = 3.141592653589793
    
    // Complex number examples
    var c64 complex64 = complex(5, 7)  // 5 + 7i
    var c128 complex128 = complex(1, 2) // 1 + 2i
    
    fmt.Printf("int: %v, type: %T\n", i, i)
    fmt.Printf("int8: %v, type: %T\n", i8, i8)
    fmt.Printf("uint: %v, type: %T\n", ui, ui)
    fmt.Printf("float32: %v, type: %T\n", f32, f32)
    fmt.Printf("float64: %v, type: %T\n", f64, f64)
    fmt.Printf("complex64: %v, type: %T\n", c64, c64)
    fmt.Printf("complex128: %v, type: %T\n", c128, c128)
}

### Type Conversion

Go requires explicit type conversion between different numeric types:

```go
var i int = 42
var f float64 = float64(i)
var u uint = uint(f)

2. Boolean Type

The boolean type bool represents two possible values: true and false.

var isValid bool = true
var isComplete bool // zero value is false

// Boolean operators
and := true && false  // false
or := true || false   // true
not := !true         // false

// Comparison operators return boolean
equals := 5 == 5     // true
greater := 10 > 5    // true
less := 5 < 10       // true

3. String Type

Strings in Go are immutable sequences of bytes. This means once a string is created, it cannot be modified.

String Immutability

str := "hello"
// str[0] = 'H'    // This will cause a compilation error
// Strings cannot be modified directly

// To modify a string, create a new one
newStr := "H" + str[1:]  // Creates a new string "Hello"

// Or convert to byte slice, modify, and convert back
bytes := []byte(str)
bytes[0] = 'H'
newStr = string(bytes)   // "Hello"

String Performance Considerations

// Bad performance: creates many temporary strings
str := ""
for i := 0; i < 1000; i++ {
    str += "a"
}

// Good performance: uses strings.Builder
var builder strings.Builder
for i := 0; i < 1000; i++ {
    builder.WriteString("a")
}
result := builder.String()

4. Array Type

Arrays in Go have a fixed length and contain elements of the same type.

// Array declaration
var numbers [5]int                     // Array of 5 integers
colors := [3]string{"red", "green", "blue"}
sizes := [...]int{1, 2, 3, 4, 5}      // Size determined by elements

// Accessing and modifying
first := numbers[0]    // Get first element
numbers[1] = 10       // Set second element

// Array operations
length := len(numbers)  // Get array length

// Multi-dimensional arrays
var matrix [3][4]int   // 3x4 matrix
matrix[0][0] = 1       // Set element

// Array iteration
for i, value := range colors {
    fmt.Printf("colors[%d] = %s\n", i, value)
}

5. Slice Type

Slices are dynamic, flexible views into arrays. Understanding their behavior when copying is crucial.

Slice Behavior and Sharing

// Original slice
original := []int{1, 2, 3, 4, 5}

// Creating a new slice reference (shallow copy)
reference := original            // Both slices share the same underlying array
reference[0] = 99               // Changes affect both slices
fmt.Println(original[0])        // Prints: 99

// Creating a slice copy (deep copy)
copySlice := make([]int, len(original))
copy(copySlice, original)       // Creates a new independent copy
copySlice[0] = 100             // Only affects the copy
fmt.Println(original[0])        // Prints: 99
fmt.Println(copySlice[0])       // Prints: 100

// Slicing behavior
slice1 := original[1:3]         // Creates a view into original
slice1[0] = 200                // Affects original because they share memory
fmt.Println(original[1])        // Prints: 200

Slice Capacity Behavior

// Creating a slice with length 3 and capacity 5
slice := make([]int, 3, 5)
fmt.Printf("Length: %d, Capacity: %d\n", len(slice), cap(slice))

// Append within capacity
slice = append(slice, 1)       // Uses same backing array

// Append beyond capacity
for i := 0; i < 3; i++ {
    slice = append(slice, i)
}
// New backing array is allocated with doubled capacity

6. Map Type

Maps in Go are reference types, which affects their copying behavior.

Map Reference Behavior

// Original map
original := map[string]int{"one": 1, "two": 2}

// Creating a reference (not a copy)
reference := original          // Both variables refer to the same map
reference["one"] = 100        // Changes affect both maps
fmt.Println(original["one"])  // Prints: 100

// To create a true copy
copy := make(map[string]int)
for k, v := range original {
    copy[k] = v
}
copy["one"] = 200            // Only affects the copy
fmt.Println(original["one"]) // Still prints: 100

Map Concurrency

// Maps are not safe for concurrent access
// Use sync.Map for concurrent access
var syncMap sync.Map

// Thread-safe operations
syncMap.Store("key", "value")
value, exists := syncMap.Load("key")
syncMap.Delete("key")

7. Struct Type

Structs are value types, but can contain reference type fields.

Struct Copy Behavior

type Person struct {
    Name string
    Age  int
    Friends []string  // Slice is a reference type
}

// Creating a struct
original := Person{
    Name: "John",
    Age: 30,
    Friends: []string{"Alice", "Bob"},
}

// Copying a struct
copy := original           // Creates a new struct with copied values
copy.Name = "Jane"        // Only affects copy
copy.Age = 25            // Only affects copy
copy.Friends[0] = "Charlie"  // Affects both because slice is a reference type

// Deep copy of struct with slices
deepCopy := Person{
    Name: original.Name,
    Age: original.Age,
    Friends: make([]string, len(original.Friends)),
}
copy(deepCopy.Friends, original.Friends)

8. Interface Type

Interfaces define behavior by declaring a set of methods.

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

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

// Interface embedding
type ReadWriter interface {
    Reader
    Writer
}

// Empty interface
var i interface{}                         // can hold values of any type
i = 42
i = "hello"
i = struct{ name string }{"John"}

// Type assertions
str, ok := i.(string)                     // safe type assertion
if ok {
    fmt.Printf("string value: %s\n", str)
}

// Type switches
switch v := i.(type) {
case int:
    fmt.Printf("Integer: %d\n", v)
case string:
    fmt.Printf("String: %s\n", v)
default:
    fmt.Printf("Unknown type\n")
}

Best Practices for Data Type Usage

  1. String Handling

    • Use strings.Builder for string concatenation in loops

    • Use bytes.Buffer when working with I/O

    • Consider using []byte for heavy string manipulation

  2. Slice Management

    • Pre-allocate slices when size is known

    • Use copy() for slice copying

    • Be careful with slice references in long-lived objects

  3. Map Usage

    • Use make() to initialize maps

    • Check for existence using two-value assignment

    • Use sync.Map for concurrent access

  4. Memory Efficiency

    • Release references to large slices or maps when no longer needed

    • Use pointer receivers for large structs

    • Consider using sync.Pool for frequently allocated objects

  5. Type Conversion

    • Always check for overflow when converting between numeric types

    • Use strconv package for string conversions

    • Handle errors in string-to-number conversions

Additional Resources

Last updated

Was this helpful?