How to Create a Pointer to a Literal Value in Go

Assign the literal to a variable first, then use the address-of operator (&) to create a pointer to that variable.

The addressability wall

You write ptr := &"hello" and the compiler immediately rejects it. You try &42 or &true and get the same wall. Go refuses to give you a pointer to a literal. This feels arbitrary at first. In C or Python, you can often coerce a literal into a reference without ceremony. Go draws a hard line. The line exists because Go separates compile-time constants from runtime memory, and it enforces that separation through a rule called addressability.

Why literals refuse to give up their location

Addressability means a value has a stable memory location that the program can reference at runtime. In Go, only a few things are addressable: variables, struct fields, slice elements, and map values. Literals and constants are not addressable. They exist as compile-time values that the compiler folds directly into instructions or stores in a read-only data section. They do not live on the stack or heap where pointers can point to them.

Think of a literal like a number printed in a textbook. The number is part of the page. You cannot point to its location on a desk because it has no independent physical presence. A variable is like a sticky note with the same number written on it. The sticky note sits on a desk. You can point to the desk location. Go forces you to create the sticky note before you can take its address.

This design keeps the garbage collector simple and prevents dangling pointers. If the compiler automatically allocated memory for every literal you tried to reference, it would need to track the lifetime of those allocations, decide when to free them, and handle edge cases where multiple parts of the program expect the literal to be identical. Go avoids that complexity by requiring explicit variable creation.

Literals are baked into the binary. Variables get a home in memory. Pointers only work with homes.

The correct pattern

You must assign the literal to a variable first. The variable gets allocated on the stack, and the & operator returns a pointer to that stack slot.

Here is the minimal pattern that satisfies the compiler:

// Assign the literal to a variable so it gets a memory location.
s := "hello"
// Take the address of the variable, not the literal.
p := &s
// p now holds a *string pointing to stack-allocated storage.
fmt.Println(*p)

The compiler sees s as a named storage location. It knows exactly where to put it. The & operator simply reads that location and returns a pointer. The pointer type matches the variable type, so p becomes *string.

Variables own their memory. Pointers borrow it.

What happens under the hood

When you write s := "hello", the compiler decides where to store the string data. Short strings often live directly in the stack frame of the function. Longer strings may point to a heap allocation if the compiler cannot prove the string will not escape. Either way, s holds a string header: a pointer to the underlying byte array and a length. The header itself lives at a specific address. That is what &s returns.

When you write &"hello", the compiler has no variable header to point to. The string data "hello" is embedded in the binary's read-only section. There is no mutable header on the stack. Creating one on the fly would require the compiler to generate hidden allocation code, track its lifetime, and potentially break constant folding optimizations. Go's designers chose to make the allocation explicit. You write the variable declaration. The compiler does the rest.

This explicitness also aligns with Go's escape analysis. The compiler traces where pointers go. If you pass &s to a function that stores it in a global map, the compiler sees the pointer escaping and promotes s to the heap. If you keep &s local, it stays on the stack. The compiler makes these decisions based on your explicit variables, not hidden literal allocations.

Explicit variables give the compiler a clear map. Hidden allocations create guesswork.

A real-world case: command-line flags

The standard library's flag package demonstrates why this rule exists in practice. The flag package needs to mutate a variable in your program based on user input. It cannot mutate a literal. It requires a pointer to a variable it can write to.

Here is how you properly set up a default value and pass a pointer to the flag parser:

// Create a variable to hold the parsed value.
// Start it with a sensible default.
port := "8080"

// Pass the address of the variable to the flag parser.
// The flag package will overwrite this memory when the user runs the program.
flag.StringVar(&port, "port", "8080", "HTTP port to listen on")

// Parse the command line and print the result.
flag.Parse()
fmt.Println("Server starting on", port)

The flag.StringVar signature requires *string. It writes the parsed value directly into the memory location you provide. If you tried to pass &"8080", the compiler would reject it because there is no writable location to update. The flag package would have nowhere to store the user's input.

APIs that need to mutate caller state demand pointers. Pointers demand addressable variables.

Common traps and compiler rejections

The most common error is trying to take the address of a constant or literal and wondering why Go is being difficult. The compiler responds with cannot take the address of "hello". This is not a bug. It is a guardrail.

Another trap is assuming you need a pointer to a string for performance. Go strings are already cheap to pass by value. A string header is just two words: a pointer and an integer. Copying it is faster than dereferencing a pointer. The community convention is clear: do not pass *string unless an API explicitly requires it. Pass the string itself. Reserve pointers for values you actually need to mutate or for satisfying interfaces that demand them.

You will also see this pattern when working with JSON unmarshaling or database drivers. Those packages need to write data into your variables. They require pointers. You create the variable, pass its address, and let the package fill it in. The pattern is consistent across the standard library.

Pointers are for mutation and API contracts. They are not for performance tricks.

When to reach for pointers

Go gives you several ways to represent data. Choosing the right one depends on what you need the value to do.

Use a literal when you need a fixed value that will never change and does not need to be referenced by multiple parts of the program. Literals are fast, require zero allocation, and the compiler can optimize them aggressively.

Use a variable when you need a value that lives in memory, can be read multiple times, or needs to be passed to functions that require an address. Variables give you a stable location and work with Go's addressability rules.

Use a pointer when you need to mutate a value from another scope, satisfy an API that requires a reference, or share a large struct without copying it. Pointers enable mutation and reduce allocation overhead for big types.

Use plain sequential code when you don't need concurrency or mutation: the simplest thing that works is usually the right thing.

Pick the representation that matches the operation. Don't force a pointer where a value will do.

Where to go next