Pointer, Dereferenced
Here’s a line that turns up, in one form or another, in nearly every Go codebase:
var _ ActiveProfilesSource = (*FakeSource)(nil)
The first time I saw it, it read like a syntax error someone forgot to delete. What is a star, a type name, and nil wrapped in a second pair of parentheses doing together? What is being dereferenced? Why two sets of parens?
The whole confusion comes from one fact most Go tutorials skip: the asterisk * is two different operators that share a glyph. They do opposite jobs, and Go decides which one you meant from where you wrote it. Once I understood that, the above line became clear and so did every other pointer expression.
Let’s start with the two operators.
& and *
Two operators, and they undo each other.
&x takes the address of x. What it returns is a pointer: a value that holds an address.
*p does the reverse. It dereferences p: it follows the address stored in p and gives you the value there.
x := 42
p := &x // & : take the address of x
fmt.Println(*p) // * : follow p to the value → 42
Because they’re inverses, *&x is just x: take x’s address, immediately follow it back. Here’s the arrangement in memory:
x is an int living at address 0xAA00, holding the value 42. p is a separate variable at address 0xBB00, and the value it holds is 0xAA00, the address of x. That’s all a pointer is: a variable whose value is an address.
Dereferencing works both ways: *p reads the value (42), and *p = 100 writes to x without ever naming x:
*p = 100
fmt.Println(x) // 100 — we changed x through p
If a pointer holds no address, it holds nil. Dereferencing it panics:
p = nil
fmt.Println(*p) // panic: invalid memory address or nil pointer dereference
But, typed holders can hold nil. That’s half the riddle.
Two meanings of *
Look at these 2 lines.
var p *int // line A
x := *p // line B
Same glyph, doing unrelated things. In line A, *int is a type: “pointer to int.” In line B, *p is an expression: “the value at p.”
The compiler tells them apart by position:
- In a type position (after
var name, in a parameter list, as a struct field’s type, afternew), where it means “pointer to.” - In an expression position (right of
=, inside a function call, anywhere a value is expected), where it means “dereference.”
English does the same with a word like left: direction or past tense of leave? You can only tell from the sentence around it.
A pointer is a type
In Go, a pointer is also a type. int and *int are two different types, as distinct as int and string. FakeSource and *FakeSource likewise.
The differentiation matters here.
As the 2 types are different, the obvious next question is what values do they hold in the default state? int defaults to 0 and *int defaults to nil.
So a nil is untyped, but when it is placed next to *int, it becomes a *int type pointer that doesn’t point anywhere.
When to use a pointer
Pointers are for sharing or mutation. Use one when:
Reach for *T when… |
Because |
|---|---|
| a function must modify the caller’s value | json.Unmarshal(data, &v) writes into v |
| the value is large and copying it is waste | func process(b *BigStruct) beats copying a kilobyte per call |
| “absent” is a real state | var owner *User — nil means no owner |
| a method mutates its receiver | func (f *FakeSource) Reset() needs a pointer receiver |
| every caller must see one shared object | singletons, state shared across goroutines |
Don’t reach for a pointer when the value is small and you’re not mutating it: an int, a string, a small struct returned fresh.
Rule of thumb: pointer receivers for methods, value parameters for small data. Consistency within a single type matters more than the abstract rule.
Making a pointer
Three ways to get a pointer, in rough order of how often you’ll write them:
x := 42
p := &x // 1. address-of an existing variable
fake := &FakeSource{IDs: nil} // 2. the most common:
// allocate a struct and grab its address in one move
q := new(int) // 3. new(T): allocate a zero T, return a *T.
// rare — &T{} reads better almost everywhere
The middle form, &FakeSource{...}, is the Go idiom: build a struct and get a pointer to it in a single expression.
Converting: T(v)
Go has no implicit numeric conversion. You can’t pass an int where a float64 is expected; you have to convert it. The syntax is T(v):
i := 42
f := float64(i) // int → float64
b := byte(65) // int → byte
s := string(b) // byte → string ("A")
data := []byte(s) // string → []byte
The shape is always T(v): a type T, then a value v in parentheses, meaning “convert v to T.” It’s resolved at compile time.
And *FakeSource is a type. So (*FakeSource)(nil) is just a T(v) conversion.
Decoding (*FakeSource)(nil)
Read it left to right:
*FakeSource— a type: “pointer to FakeSource.”(*FakeSource)(nil)— aT(v)conversion whereTis*FakeSourceandvisnil.- The result is a
*FakeSourcewhose value isnil. A typed nil.
The parens are not decoration. Drop them and the meaning flips:
*FakeSource(nil) // parsed as *(FakeSource(nil))
// → apply * (dereference) to FakeSource(nil)
// → nonsense; the compiler rejects it
(*FakeSource)(nil) // parsed as (*FakeSource)(nil)
// → convert nil to the type *FakeSource
// → a typed nil pointer
Without the parens, the asterisk is in an expression position, so Go reads it as dereference and applies it to FakeSource(nil). With them, (*FakeSource) parses as a single type, and the whole expression is a conversion.
Typed nil vs &FakeSource{}
Both of these compile, and both pass the assertion:
var _ ActiveProfilesSource = (*FakeSource)(nil) // typed nil
var _ ActiveProfilesSource = &FakeSource{} // a real, empty struct
The assertion is a compile-time check. var _ = … throws the value away (the blank identifier _ binds to nothing). The line exists only to make the compiler confirm that *FakeSource satisfies ActiveProfilesSource. The day that stops being true, the build breaks here.
Since the value is never used, the only difference between the two forms is cost:
(*FakeSource)(nil) is a typed nil — a pointer at nothing. No struct, zero allocation. &FakeSource{} allocates a FakeSource on the heap, zeroes its fields, takes its address, and then discards the result.
For a check that runs once at compile time the waste is microscopic. But the typed-nil form is the convention because it states the intent: I want the compiler to verify a type, not a value. (*FakeSource)(nil) does that and allocates nothing.
Mutation needs a pointer
Because pointer-ness lives in the type, a Go signature tells you what a function is allowed to touch:
func process(s Source) {
// s is a copy. Reassign it,
// overwrite its fields — the
// caller's value is untouched.
}
func process(s *Source) {
// s is an address. Writes
// through it reach back and
// change the caller's value.
}
The * is in the parameter list, making its intention clear. Compare Java, where every object is a reference: void process(Source s) doesn’t tell you whether s will be mutated. You have to read the implementation. Go made the opposite trade. It put the marker into the type and made you write the asterisk.
One thing the asterisk doesn’t save you from: a value that contains a slice, map, or pointer still shares that inner storage. Copying the outer struct copies the reference, not the thing it points at. That’s a different anatomy lesson.
The price is the dual-meaning glyph. The payoff is that you can read (*FakeSource)(nil), and any other Go signature.