Appendix B. Variables and pointers
Understanding Go’s variables and pointers is key to writing correct, efficient programs. Go is statically typed, so every variable must be declared and keeps the same type for its entire lifetime. Uninitialized variables get a safe zero value. You can declare with var (optionally specifying a type) or with the short := form (which infers the type from the initializer). Assignments require matching types, and Go insists on explicit numeric conversions to avoid subtle bugs.
Pointers let you share and mutate data without copying it by holding the memory address of a value. The address-of operator (&) produces a variable’s address, and the dereference operator (*) reads the value at that address. Pointers are typed and can be nil (their zero value), which you should check before dereferencing to avoid panics. For safety, Go disallows pointer arithmetic and relies on the garbage collector to manage memory, while many built-in types can be checked against nil to determine initialization.
Go always uses pass-by-value: arguments are copied when passed to functions. Passing a pointer copies only the pointer, not the underlying value, so the callee can update the original data through that address; pointer copies are small and cheap. It is safe to return pointers to local variables because the compiler will allocate them on the heap when their lifetime must outlive the function. This model makes stack versus heap details mostly transparent while enabling efficient, controlled mutation via pointers.
Figure B.1 A pointer variable points to a variable's memory address.
Figure B.2 * returns the pointed value while & finds the variable's address.
Figure B.3 Pointers enable us to mutate original values without copying them.
B.3 Exercises
- Declare a few variables. Try assigning different types of values to every variable you create and observe the compiler errors. Increment integer variables. Pass and mutate these variables in a function and check if your changes are visible outside.
- Declare a pointer variable to one of these variables and pass it to functions. Mutate the variables through the pointers and observe your changes from the caller.
FAQ
How do I declare variables in Go, with and without an initial value?
Use var for declarations. Without an initial value, the variable gets its type’s zero value.
var title string // zero value: "" var pages int // zero value: 0
Inside functions, you can use short declaration to both declare and initialize:
title := "Go by Example" // type inferred as string
What’s the difference between := and = in Go?
:= is short declaration (declare + initialize) and is only allowed inside functions. = is assignment to an already-declared variable.
name := "Go" // declare and initialize name = "Golang" // assign a new value
How does Go infer types from literals, and how can I choose a different type?
Go infers types from literal defaults: string → string, 42 → int, 3.1 → float64, true → bool. To choose a different type, declare it explicitly.
var pages int64 = 1000 // literal 1000 would default to int count := 500 // count is int
Why do I get a type mismatch when assigning ints of different sizes, and how do I fix it?
Types like int and int64 are distinct. Go requires explicit conversion to prevent subtle bugs.
var total int64 n := 500 // n is int // total = n // compile error total = int64(n) // explicit conversion
What is a pointer in Go and how do I get one?
A pointer holds the memory address of a value. Use & to take an address and *T to declare a pointer type.
counter := 42 ptr := &counter // ptr has type *int; points to counter
What does dereferencing (*) do, and what happens if I dereference a nil pointer?
Dereferencing (*ptr) reads or writes the value a pointer points to. Dereferencing a nil pointer panics.
var p *int p == nil // true // fmt.Println(*p) // panic: nil pointer dereference x := 10 p = &x fmt.Println(*p) // 10
Are pointers type-safe in Go?
Yes. A *T can only point to values of type T. This prevents accidental or malicious memory misuse. Many types are or contain pointers and can be compared to nil (e.g., slices, maps, functions, interfaces, channels).
Is Go pass-by-value or pass-by-reference, and how do I mutate a caller’s value?
Go is always pass-by-value. To mutate a caller’s value, pass a pointer. The pointer itself is still passed by value, but it points to the original data.
func incrVal(v int) { v++ } // does not affect caller
func incrPtr(p *int) { *p++ } // mutates original
counter := 42
incrVal(counter) // counter still 42
incrPtr(&counter) // counter is now 43Is it safe to return a pointer to a local variable?
Yes in Go. If a value must outlive its function, the compiler typically allocates it on the heap (escape analysis), and the garbage collector keeps it alive.
func newInt() *int {
n := 42
return &n // safe in Go
}Does Go allow pointer arithmetic?
No. Go disallows pointer arithmetic to preserve safety and enable garbage collection. You can do low-level operations via the unsafe package, but it’s discouraged as it breaks type and memory safety guarantees.
Go by Example ebook for free