Skip to main content
โšก Calmops

Go Type System: Basics and Type Inference

Go Type System: Basics and Type Inference

Go’s type system is one of its defining features. It’s statically typed but with excellent type inference, making code concise without sacrificing safety. This guide covers Go’s type system fundamentals and best practices.

Basic Types

Numeric Types

package main

import "fmt"

func main() {
    // Signed integers
    var i8 int8 = 127           // -128 to 127
    var i16 int16 = 32767       // -32768 to 32767
    var i32 int32 = 2147483647  // -2^31 to 2^31-1
    var i64 int64 = 9223372036854775807  // -2^63 to 2^63-1
    var i int = 42              // Platform-dependent (32 or 64 bit)
    
    // Unsigned integers
    var u8 uint8 = 255          // 0 to 255
    var u16 uint16 = 65535      // 0 to 65535
    var u32 uint32 = 4294967295 // 0 to 2^32-1
    var u64 uint64 = 18446744073709551615  // 0 to 2^64-1
    var u uint = 42             // Platform-dependent
    var b byte = 255            // Alias for uint8
    
    // Floating point
    var f32 float32 = 3.14      // 32-bit float
    var f64 float64 = 3.14159   // 64-bit float (default)
    
    // Complex numbers
    var c64 complex64 = 1 + 2i
    var c128 complex128 = 1 + 2i
    
    fmt.Println(i8, i16, i32, i64, i)
    fmt.Println(u8, u16, u32, u64, u, b)
    fmt.Println(f32, f64)
    fmt.Println(c64, c128)
}

String and Boolean Types

package main

import "fmt"

func main() {
    // String
    var s string = "Hello, Go!"
    
    // Boolean
    var b bool = true
    var b2 bool = false
    
    // Rune (character)
    var r rune = 'A'  // Alias for int32
    
    // Byte
    var by byte = 65  // Alias for uint8
    
    fmt.Println(s)
    fmt.Println(b, b2)
    fmt.Println(r, by)
}

Type Inference

Variable Declaration with Inference

package main

import "fmt"

func main() {
    // Type inference with :=
    x := 42              // int
    y := 3.14            // float64
    z := "hello"         // string
    b := true            // bool
    
    fmt.Printf("%T\n", x)  // int
    fmt.Printf("%T\n", y)  // float64
    fmt.Printf("%T\n", z)  // string
    fmt.Printf("%T\n", b)  // bool
}

Inference Rules

package main

import "fmt"

func main() {
    // Integer literals default to int
    a := 42
    fmt.Printf("%T\n", a)  // int
    
    // Float literals default to float64
    b := 3.14
    fmt.Printf("%T\n", b)  // float64
    
    // String literals are strings
    c := "hello"
    fmt.Printf("%T\n", c)  // string
    
    // Boolean literals are bool
    d := true
    fmt.Printf("%T\n", d)  // bool
    
    // Complex literals default to complex128
    e := 1 + 2i
    fmt.Printf("%T\n", e)  // complex128
}

Explicit Type Specification

package main

import "fmt"

func main() {
    // Explicit type with var
    var x int32 = 42
    var y float32 = 3.14
    var z string = "hello"
    
    // Explicit type with :=
    a := int8(42)
    b := float32(3.14)
    c := byte(65)
    
    fmt.Printf("%T: %v\n", x, x)  // int32: 42
    fmt.Printf("%T: %v\n", y, y)  // float32: 3.14
    fmt.Printf("%T: %v\n", z, z)  // string: hello
    fmt.Printf("%T: %v\n", a, a)  // int8: 42
    fmt.Printf("%T: %v\n", b, b)  // float32: 3.14
    fmt.Printf("%T: %v\n", c, c)  // uint8: 65
}

Type Conversion

Explicit Type Conversion

package main

import (
    "fmt"
    "strconv"
)

func main() {
    // Numeric conversions
    var i int = 42
    var f float64 = float64(i)
    var u uint = uint(f)
    
    fmt.Println(f)  // 42
    fmt.Println(u)  // 42
    
    // String conversions
    s := "123"
    num, _ := strconv.Atoi(s)  // String to int
    fmt.Println(num)           // 123
    
    s2 := strconv.Itoa(456)    // Int to string
    fmt.Println(s2)            // "456"
    
    // Float conversions
    f2, _ := strconv.ParseFloat("3.14", 64)
    fmt.Println(f2)            // 3.14
    
    s3 := strconv.FormatFloat(3.14, 'f', 2, 64)
    fmt.Println(s3)            // "3.14"
}

String and Byte Conversions

package main

import "fmt"

func main() {
    // String to bytes
    s := "Hello"
    bytes := []byte(s)
    fmt.Println(bytes)  // [72 101 108 108 111]
    
    // Bytes to string
    s2 := string(bytes)
    fmt.Println(s2)     // Hello
    
    // String to runes
    runes := []rune(s)
    fmt.Println(runes)  // [72 101 108 108 111]
    
    // Runes to string
    s3 := string(runes)
    fmt.Println(s3)     // Hello
}

Type Declarations

Named Types

package main

import "fmt"

// Declare a new type based on int
type Age int

// Declare a new type based on string
type Email string

// Declare a new type based on struct
type Person struct {
    Name string
    Age  Age
}

func main() {
    var age Age = 30
    var email Email = "[email protected]"
    
    person := Person{
        Name: "Alice",
        Age:  25,
    }
    
    fmt.Println(age)
    fmt.Println(email)
    fmt.Println(person)
}

Type Aliases

package main

import "fmt"

// Type alias (Go 1.9+)
type MyInt = int
type MyString = string

func main() {
    var x MyInt = 42
    var s MyString = "hello"
    
    // MyInt and int are interchangeable
    var y int = x
    
    fmt.Println(x, y)
    fmt.Println(s)
}

Difference Between Type Declaration and Alias

package main

import "fmt"

// Type declaration - creates a new type
type UserId int

// Type alias - creates an alias for existing type
type StringAlias = string

func main() {
    var id UserId = 123
    var s StringAlias = "hello"
    
    // โŒ This won't compile - UserId and int are different types
    // var x int = id
    
    // โœ… This compiles - StringAlias and string are the same type
    var str string = s
    
    fmt.Println(id)
    fmt.Println(str)
}

Zero Values

Every type has a zero value:

package main

import "fmt"

func main() {
    var i int
    var f float64
    var s string
    var b bool
    var p *int
    var sl []int
    var m map[string]int
    
    fmt.Println(i)   // 0
    fmt.Println(f)   // 0
    fmt.Println(s)   // "" (empty string)
    fmt.Println(b)   // false
    fmt.Println(p)   // <nil>
    fmt.Println(sl)  // []
    fmt.Println(m)   // map[]
}

Type Assertions

Type Assertions with Interface

package main

import "fmt"

func main() {
    var i interface{} = "hello"
    
    // Type assertion
    s, ok := i.(string)
    if ok {
        fmt.Println("String:", s)
    }
    
    // Type assertion without ok
    s2 := i.(string)
    fmt.Println("String:", s2)
    
    // Type assertion with wrong type (panics)
    // n := i.(int)  // panic: interface conversion: interface {} is string, not int
}

Type Switches

package main

import "fmt"

func describe(i interface{}) {
    switch v := i.(type) {
    case int:
        fmt.Printf("Integer: %d\n", v)
    case string:
        fmt.Printf("String: %s\n", v)
    case bool:
        fmt.Printf("Boolean: %v\n", v)
    default:
        fmt.Printf("Unknown type: %T\n", i)
    }
}

func main() {
    describe(42)
    describe("hello")
    describe(true)
    describe(3.14)
}

Numeric Type Ranges

package main

import (
    "fmt"
    "math"
)

func main() {
    // Integer ranges
    fmt.Println("int8:", math.MinInt8, "to", math.MaxInt8)
    fmt.Println("int16:", math.MinInt16, "to", math.MaxInt16)
    fmt.Println("int32:", math.MinInt32, "to", math.MaxInt32)
    fmt.Println("int64:", math.MinInt64, "to", math.MaxInt64)
    
    // Unsigned integer ranges
    fmt.Println("uint8:", 0, "to", math.MaxUint8)
    fmt.Println("uint16:", 0, "to", math.MaxUint16)
    fmt.Println("uint32:", 0, "to", math.MaxUint32)
    fmt.Println("uint64:", 0, "to", math.MaxUint64)
    
    // Float ranges
    fmt.Println("float32:", math.SmallestNonzeroFloat32, "to", math.MaxFloat32)
    fmt.Println("float64:", math.SmallestNonzeroFloat64, "to", math.MaxFloat64)
}

Type Checking

package main

import (
    "fmt"
    "reflect"
)

func main() {
    x := 42
    y := 3.14
    z := "hello"
    
    // Using %T format verb
    fmt.Printf("%T\n", x)  // int
    fmt.Printf("%T\n", y)  // float64
    fmt.Printf("%T\n", z)  // string
    
    // Using reflect package
    fmt.Println(reflect.TypeOf(x))  // int
    fmt.Println(reflect.TypeOf(y))  // float64
    fmt.Println(reflect.TypeOf(z))  // string
}

Best Practices

โœ… Good Practices

  1. Use type inference - Let Go infer types when obvious
  2. Be explicit when needed - Specify types for clarity
  3. Use named types - Create types for domain concepts
  4. Avoid unnecessary conversions - Keep types consistent
  5. Use appropriate numeric types - Choose based on range needed
  6. Handle type assertions safely - Always check ok value
  7. Document type choices - Explain why specific types are used

โŒ Anti-Patterns

// โŒ Bad: Unnecessary type conversions
var x int = 42
var y int64 = int64(x)
var z int = int(y)

// โœ… Good: Keep types consistent
var x int = 42
var y int = x

// โŒ Bad: Using int64 for everything
var age int64 = 30
var count int64 = 100

// โœ… Good: Use appropriate types
var age int = 30
var count int = 100

// โŒ Bad: Ignoring type assertion errors
s := i.(string)  // Panics if i is not string

// โœ… Good: Check type assertion
s, ok := i.(string)
if !ok {
    // Handle error
}

Summary

Go’s type system is powerful and flexible:

  • Understand basic types and their ranges
  • Leverage type inference for concise code
  • Use explicit types for clarity
  • Create named types for domain concepts
  • Handle type conversions safely
  • Use type assertions and switches for interface{} values

Master the type system for robust Go programs.

Comments