Variables & Types
Static typing, zero values, explicit conversion.
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:
- Method receivers that mutate the struct:
func (u *User) Rename(s string). - Optional values where
nilmeans "absent":*stringinstead ofstring.
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.