01 — Basics
The parts of Go where the mental model differs from C: zero values, integer overflow,
multiple return values, slices, append, and explicit conversions. Every
example is live — edit it and press Run.
1. Zero values
Every declared variable in Go is initialised to its type's zero value
— you never read an indeterminate value the way you can with an uninitialised local in C.
Numbers start at 0, strings at "", booleans at
false.
package main
import "fmt"
func main() {
var x int
var f float64
var s string
var b bool
fmt.Printf("%d %g %q %t\n", x, f, s, b)
}
Output:
0 0 "" false
2. Integer overflow is defined
When arithmetic exceeds a type's range, Go wraps modulo 2ⁿ for both signed and unsigned integers — no panic. This is the two's-complement wraparound you know from C's unsigned types, but Go defines it for signed integers too (where C leaves it undefined). An out-of-range constant, by contrast, is a compile error.
package main
import "fmt"
func main() {
var x int8 = 127
x++ // wraps to -128
var u uint8 = 255
u++ // wraps to 0
fmt.Println(x, u)
}
Output:
-128 0
3. Multiple return values
A Go function can return several values directly, named in the signature. In C you'd
return one value and use pointer out-parameters for the rest. Here MinMax
returns the smallest, the largest, and an ok flag that is false for an
empty slice — the value, ok idiom you'll see throughout Go.
package main
import "fmt"
func MinMax(xs []int) (min, max int, ok bool) {
if len(xs) == 0 {
return // bare return: 0, 0, false
}
min, max, ok = xs[0], xs[0], true
for _, v := range xs[1:] {
if v < min {
min = v
}
if v > max {
max = v
}
}
return
}
func main() {
lo, hi, ok := MinMax([]int{3, 1, 4, 1, 5, 9, 2, 6})
fmt.Println(lo, hi, ok)
fmt.Println(MinMax(nil))
}
Output:
1 9 true
0 0 false
The names (min, max int, ok bool) are return declarations, not
inputs: they're pre-declared variables starting at their zero values, and a plain
return hands back whatever they hold.
4. Slices and append
A slice is a lightweight window onto a backing array: a header of
{pointer, length, capacity}. Taking a sub-slice shares the same storage —
no copy — so writing through one view is visible through the other. And
append may reallocate, so you must capture its return value
(s = append(s, x)).
package main
import "fmt"
func main() {
s := []int{10, 20, 30, 40}
mid := s[1:3] // window over elements 1,2 — same backing array
mid[0] = 99 // visible in s too
fmt.Println(s)
fmt.Println(len(s), cap(s))
var xs []int // nil slice: usable as empty
xs = append(xs, 1) // append to nil is fine
xs = append(xs, 2)
fmt.Println(xs)
}
Output:
[10 99 30 40]
4 4
[1 2]
5. No implicit numeric conversions
Go never converts between numeric types automatically — you write T(x).
That makes truncation visible, but the conversion itself is unchecked, just like a C
cast: an out-of-range integer keeps its low bits, and a float truncates toward zero.
package main
import "fmt"
func main() {
var big int32 = 300
var small int8 = int8(big) // 300 doesn't fit int8 -> wraps to 44
var neg int32 = -1
var u uint8 = uint8(neg) // -1 -> 255 (low 8 bits)
var fl float64 = 3.9
var n int = int(fl) // truncates toward zero -> 3
fmt.Println(small, u, n)
}
Output:
44 255 3
Recap
| Concept | In Go |
|---|---|
| Uninitialised variable | Impossible — zero value (0, "", false, nil) |
| Integer overflow | Wraps modulo 2ⁿ at runtime; out-of-range constant is a compile error |
| Several results | Multiple return values (v, ok) |
| Dynamic sequence | []T — a {ptr, len, cap} window |
| Grow a slice | s = append(s, x) — always reassign |
| Mix numeric types | Explicit T(x); unchecked like a C cast |
Next: 02 — Strings & Runes.