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
- Use type inference - Let Go infer types when obvious
- Be explicit when needed - Specify types for clarity
- Use named types - Create types for domain concepts
- Avoid unnecessary conversions - Keep types consistent
- Use appropriate numeric types - Choose based on range needed
- Handle type assertions safely - Always check ok value
- 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