golang · level 1

Variables & Types

Static typing, zero values, explicit conversion.

100 XP

Go Variables & Types

Go is statically typed — every variable has a type known at compile time, and the compiler enforces it. The compile error catches a whole class of bugs Python won't notice until production.

Declaring variables

Three forms:

var x int = 5          // explicit type + value
var y = 5              // type inferred from value (still int)
z := 5                 // short form, only inside functions

:= is the everyday form. Use var at package scope (where := isn't allowed) or when you want a zero-valued declaration:

var name string        // name == ""  (zero value)
var count int          // count == 0
var users []string     // users == nil (empty slice)

Zero values

Every type has a zero value — what you get if you declare without initialising. There is no "undefined" in Go.

Type Zero value
numeric (int, float64, etc.) 0
bool false
string ""
pointer / slice / map / chan / func / interface nil

This means var x int; if x == 0 { ... } always works — no need to check "did this get initialised?"

Built-in types

// Integers — sized variants exist (int8, int16, int32, int64).
// `int` is platform-sized (32 or 64 bits).
var a int = 42
var b int64 = 1_000_000_000_000

// Floats: float32 and float64 (use float64 unless you have a reason).
var pi float64 = 3.14159

// Strings — immutable, UTF-8 encoded.
var s string = "héllo"
len(s)        // 6 (BYTES, not characters)
utf8.RuneCountInString(s) // 5 (characters)

// Booleans.
var ok bool = true

// Byte slices — for binary data.
data := []byte("hello")

int is platform-dependent (int32 on 32-bit systems, int64 on 64-bit). Use sized types (int32, int64) when the size matters for serialisation or interop.

Type conversion is explicit

Go won't auto-convert between numeric types. You must convert explicitly:

var i int = 42
var f float64 = float64(i)   // OK
var f2 float64 = i           // ERROR: cannot use i (int) as float64

Pedantic? Yes. Saves you from int + float = float-with-precision-loss bugs.

Constants

const Pi = 3.14159
const Greeting = "Hello"

const (
    StatusOK         = 200
    StatusNotFound   = 404
    StatusServerError = 500
)

Constants are evaluated at compile time. They can be untyped (const x = 5 — the type is inferred at use site) or typed (const x int = 5).

Type aliases

Sometimes you want a named type that's distinct from its underlying type:

type UserID int
type Email string

func (e Email) Domain() string {
    parts := strings.SplitN(string(e), "@", 2)
    if len(parts) < 2 { return "" }
    return parts[1]
}

UserID is a distinct type from int — passing one where the other is expected is a compile error. Type aliases prevent the "I passed the wrong int" bug.

Pointers (lightly)

Go has pointers but no pointer arithmetic. The & and * operators:

x := 42
p := &x       // p is *int (pointer to int)
*p = 99       // dereference + assign — x is now 99

You'll mainly see pointers in two places:

  1. Method receivers that mutate the struct: func (u *User) Rename(s string).
  2. Optional values where nil means "absent": *string instead of string.

What Go's type system gives you

The big win isn't strictness for its own sake — it's that the compiler answers questions you'd otherwise have to test for:

  • "Did I pass the right thing?" — yes, the compiler checked.
  • "Is this nil?" — only if the type allows nil.
  • "Will this serialise correctly?" — the field type tells you.

You write more characters at definition time, you write fewer tests for type confusion at runtime.