The addressability rule
You write a quick struct literal in Go, slap an ampersand in front to get a pointer, and the compiler yells. You've done this a thousand times in Python or JavaScript where references are implicit and objects float around freely. In Go, the ampersand is a real operator with strict rules. You try &Config{Port: 8080} and get cannot take address of Config literal. You try &getUser() and get cannot take address of getUser(). The compiler is blocking you, but the fix is usually a single variable assignment.
Go requires a value to be addressable before you can use the & operator. Addressable means the value lives at a specific memory location that the program can reference for a predictable duration. Variables are addressable. Struct fields of addressable structs are addressable. Slice elements are addressable. Map values are not addressable. Literals are not addressable. Function return values are not addressable. Constants are not addressable.
The compiler enforces this to keep memory management predictable. If you could take the address of a temporary value that vanishes instantly, you would end up with a dangling pointer pointing to garbage. Go prevents that at compile time. The error is a safety net, not a speed bump.
Why Go enforces this
Addressability ties directly to how Go manages memory. When you declare a variable, the compiler allocates space for it. That space has an address. When you write a literal like Config{Port: 8080}, the value exists only for the duration of the expression. It has no variable to hold it. If Go allowed you to take its address, the pointer would point to memory that gets reused or invalidated immediately.
Function returns work the same way. getUser() produces a value. That value is temporary. It lives in a register or a temporary stack slot until the assignment completes. Taking its address would capture a location that ceases to be valid the moment the expression ends.
The compiler rejects these attempts with clear errors. You get cannot take address of Config literal or cannot take address of getUser(). The message tells you exactly what is wrong: the operand is not a stable location. The solution is to give the value a home. Assign it to a variable. Variables have homes.
Minimal fix
The fix is to assign the value to a variable first. The variable is addressable. Then you can take the address of the variable.
package main
import "fmt"
// Config holds settings for a service.
type Config struct {
Port int
Name string
}
// loadConfig returns a Config value.
func loadConfig() Config {
return Config{Port: 8080, Name: "api"}
}
func main() {
// This fails: literals are temporary and have no stable address.
// cfg := &Config{Port: 8080} // Error: cannot take address of Config literal
// Fix: assign to a variable first. The variable is addressable.
cfg := Config{Port: 8080, Name: "api"}
ptr := &cfg
fmt.Println(ptr.Name)
// This fails: function returns are temporary values.
// ptr2 := &loadConfig() // Error: cannot take address of loadConfig()
// Fix: capture the return value in a variable.
loaded := loadConfig()
ptr2 := &loaded
fmt.Println(ptr2.Port)
}
The variable cfg lives on the stack. It has an address. &cfg is safe. The same logic applies to loaded. The function returns a value, the assignment stores it in loaded, and &loaded points to that storage.
Realistic scenario
In real code, you often need pointers to pass data to functions that expect pointer receivers or to allow modification. The pattern of assigning to a variable first is idiomatic Go.
package main
import (
"encoding/json"
"fmt"
"log"
)
// ServerConfig defines server parameters.
type ServerConfig struct {
Host string `json:"host"`
Port int `json:"port"`
}
// parseConfig reads JSON and returns a ServerConfig.
func parseConfig(data []byte) ServerConfig {
var cfg ServerConfig
json.Unmarshal(data, &cfg)
return cfg
}
// Validate checks the config and returns an error if invalid.
// The receiver is a pointer because validation might modify internal state
// or because the convention is to use pointer receivers for non-trivial structs.
func (c *ServerConfig) Validate() error {
if c.Port < 1 || c.Port > 65535 {
return fmt.Errorf("invalid port: %d", c.Port)
}
if c.Host == "" {
return fmt.Errorf("host cannot be empty")
}
return nil
}
func startServer() {
// Simulate reading config from a file or environment.
rawData := []byte(`{"host": "localhost", "port": 9090}`)
// Common mistake: trying to pass a pointer to the parsed config directly.
// srv := &parseConfig(rawData) // Error: cannot take address of parseConfig(rawData)
// Correct approach: capture the value, then take the address.
// This is idiomatic Go. The variable holds the config, and the pointer
// allows you to pass it to methods that expect *ServerConfig.
cfg := parseConfig(rawData)
srv := &cfg
// Now you can call methods on the pointer.
if err := srv.Validate(); err != nil {
log.Fatal(err)
}
// Real-world note: if you need to modify the config later,
// the pointer allows in-place updates without copying the struct.
srv.Port = 9091
fmt.Println("Updated port:", srv.Port)
}
func main() {
startServer()
}
The variable cfg holds the parsed config. srv is a pointer to cfg. You can call srv.Validate() because the method set of *ServerConfig includes methods defined on the pointer receiver. This pattern appears everywhere in Go codebases. Assign first, then take the address.
Convention aside: when defining methods, the receiver name is usually one or two letters matching the type. Use (c *ServerConfig) Validate(), not (this *ServerConfig) or (self *ServerConfig). The type is already clear from the signature. Short names keep the code clean.
The map trap
Maps are the biggest source of confusion for developers coming from other languages. In many languages, map lookups return references. In Go, map values are not addressable. You cannot take the address of m[key].
package main
import "fmt"
func main() {
scores := map[string]int{"alice": 10, "bob": 20}
// This fails: map values are not addressable.
// ptr := &scores["alice"] // Error: cannot take address of map index expression
// To modify a map value, assign directly.
scores["alice"] = 15
fmt.Println(scores)
}
The compiler rejects &scores["alice"] with cannot take address of map index expression. The reason is internal. Map values can move around in memory during resizes. If you add enough entries, the map grows and copies all values to a new bucket array. If Go gave you a pointer to a map value, that pointer could become invalid the moment the map grows. You would have a dangling pointer without any warning.
The safe pattern is to read the value, modify it, and write it back. Or use direct assignment. If you absolutely need a pointer to a value inside a map, store pointers in the map from the start. map[string]*Config works because the pointer itself is the value. The pointer is addressable, and the map stores the pointer. The pointed-to struct lives on the heap and stays stable.
Maps move. Don't point at map values.
Escape analysis and performance
Taking the address of a variable triggers escape analysis. The compiler checks whether the pointer "escapes" the current function. If the pointer stays inside the function, the variable stays on the stack. Stack allocation is fast. If you return the pointer or store it in a global variable, the variable must survive after the function returns. The compiler moves the variable to the heap.
This happens automatically. You do not manage it. The addressability rule ensures the compiler can track lifetimes accurately. If literals were addressable, the compiler couldn't decide where to put them. By forcing variables, the compiler has a clear anchor for escape analysis.
You can see escape analysis in action with go build -gcflags="-m". The output shows which variables escape to the heap. In most code, the performance difference is negligible. Write clear code. The compiler handles memory.
Pitfalls and edge cases
Interface values add another layer of complexity. An interface value is a header containing a type pointer and a data pointer. Taking the address of an interface gives you a pointer to the header, not the underlying value.
package main
import "fmt"
func main() {
var i interface{} = 42
// This takes the address of the interface header, not the int.
ptr := &i
fmt.Printf("%T\n", ptr) // *interface {}
// If you need a pointer to the underlying value, the interface
// should already hold a pointer.
val := 42
var j interface{} = &val
// Now you can type assert to *int.
if p, ok := j.(*int); ok {
fmt.Println("Got pointer:", *p)
}
}
The variable i holds an interface. &i is *interface{}. This is rarely what you want. If you need to manipulate the underlying value via pointer, store a pointer in the interface. j holds *int. You can type assert to *int and get the pointer back.
Constants are never addressable. &42 is impossible. The compiler says cannot take address of 42. Constants are baked into the binary. They have no memory location at runtime.
Decision matrix
Use a variable assignment when you have a literal or function result and need a pointer. Use a map of pointers when you need concurrent or shared mutation of map entries. Use direct map assignment when you are updating a single value; m[key] = newValue is faster and clearer than pointer indirection. Use &variable when passing data to a function that requires a pointer receiver or modifies the argument. Use an interface holding a pointer when you need dynamic dispatch on a mutable value.
Variables have addresses. Temporaries do not.