← Go, End to End

Go, End to End

Pointers in Go

If you've written C or C++, Go pointers will feel like a relief: same core idea (a variable holding a memory address), almost none of the danger. If you haven't, the core idea is simple enough to pick up from scratch: a pointer just holds the address of another variable, instead of holding a value directly.

The basics

`&x` gives you the address of `x`, a pointer to it. `*p` dereferences a pointer, giving you the value it points to. That's the entire vocabulary.

go · live, editable, runnable

What Go leaves out, on purpose

No pointer arithmetic. You can't do `p + 1` to walk to "the next int in memory" the way you can in C, that entire category of buffer-overrun bug simply doesn't compile. No pointer casting between unrelated types either, no `reinterpret_cast` equivalent for plain pointers. Go trades away some low-level control here in exchange for a much smaller footgun surface, and for the overwhelming majority of application code, you don't miss it.

new vs &T{}

`new(T)` allocates zeroed memory for a `T` and returns a pointer to it. In practice, almost nobody writes `new` for structs, `&T{}` (a composite literal, address-of) does the same job while also letting you set fields inline, and it's what you'll see in real code.

go
p1 := new(int)     // *int pointing at a zeroed int, rarely used directly for structs
p2 := &Point{X: 1}  // far more common: literal + address-of, and you get initialization too

Pointer vs value: when to use which

This comes down to two questions: does the function need to mutate what's passed in, and is the value expensive to copy? If either answer is yes, use a pointer. If both answers are no, a plain value is simpler and just as fast for anything small (an int, a small struct, a couple of fields).

  • Pass by value when the function only reads the data and the type is small, copies are cheap and the code stays simpler to reason about (no aliasing to worry about).
  • Pass by pointer when the function needs to mutate the caller's data, or the value is large enough that copying it on every call is real, measurable overhead.
  • Slices, maps, and channels are already reference-like under the hood (they carry a pointer to their backing data internally), so you rarely need to pass `*[]int` or `*map[string]int`, passing the slice/map itself already shares the underlying data.

The main real danger: nil pointer dereferences

A pointer's zero value is `nil`. Dereferencing a nil pointer panics immediately, at the exact line where you tried to use it, which is at least honest compared to some languages' more mysterious null-related failures, but it's still the most common runtime crash in real Go programs.

go · live, editable, runnable

The fix is always the same: check for nil before dereferencing, right at the boundary where a function might legitimately return one. `if cfg == nil { ... }` before you touch `cfg.Timeout`. There's no shortcut around this, Go doesn't have optionals or a compiler-enforced null check, it's on you to handle the nil case anywhere a pointer might genuinely be nil.

Escape analysis, briefly

In C, whether a variable lives on the stack or the heap is something you control, mostly by choosing whether to use `malloc`. In Go, you never make that choice explicitly. The compiler's escape analysis decides for you: if it can prove a value never needs to outlive the function that created it, it stays on the stack (cheap). If a pointer to it escapes (gets returned, stored somewhere longer-lived, captured by a goroutine), it moves to the heap automatically.

You don't need to hand-manage this, but it's worth knowing it exists so `&Config{...}` returned from a function doesn't look alarming: the compiler sees the pointer escaping the function and heap-allocates it correctly, no dangling pointer risk the way returning the address of a local variable would be in C.

Try it yourself: fix the nil dereference

Rerun the loadConfig example above, then fix main() to check for nil before printing, and rerun with fromFile set to false to confirm it no longer panics:

go · live, editable, runnable