skip to content

In Go, what does the composite literal &Point{X: 1} evaluate to?

level: middleimportance: should knowfreq 46%

answer

  1. One expression, no temporary variable
  2. Legal even though literals are not variables
  3. Same result as new, plus field initialisation
  4. The ampersand does not choose stack or heap
  5. Inside []*T{...} the ampersand disappears

basics

~20 s

&Point{X: 1} allocates a new Point with X set to 1 and the other fields zeroed, and evaluates to a *Point. Go lets you take the address of a composite literal directly, so no temporary variable is needed.

solid answer

~50 s

`&Point{X: 1}` builds a fresh `Point` value, initialises the named fields, zeroes the rest, and yields a `*Point` to it. It is exactly equivalent to `p := Point{X: 1}; return &p`, and it is the idiomatic constructor body in Go — `func New() *Encoder { return &Encoder{w: w} }`. Two details are worth knowing. First, the `&` says nothing about where the value lives: escape analysis at compile time decides stack or heap, and `go build -gcflags=-m` will tell you which. Second, inside an outer composite literal whose element type is `*T`, the `&T` may be elided, so `[]*Point{{X: 1}, {X: 2}}` is a two-element slice of distinct `*Point` values. Compared with `new(Point)`, which also returns a `*Point`, the literal form can set fields in the same expression, which is why `&T{...}` has almost entirely displaced `new` for structs.

code

go · 13 lines
go
type Point struct{ X, Y int }

p := &Point{X: 1} // *Point; Y is zero

// longhand for the same thing
tmp := Point{X: 1}
q := &tmp

// element type is *Point, so &Point is elided on each element
pts := []*Point{{X: 1}, {X: 2}}

fmt.Printf("%+v\n", p) // prints &{X:1 Y:0}
_, _ = q, pts

go deeper

for a junior

Know that &T{...} is one expression that both builds and addresses a value, and that omitted fields are zeroed. Recognise it as the normal body of a function returning a pointer.

for a middle

Explain the equivalence with a named temporary, the elision of &T inside a []*T literal, and why &T{...} has displaced new for structs.

for a senior

Be ready to talk about allocation. Explain that escape analysis, not the ampersand, decides stack versus heap, and show that you have read go build -gcflags=-m output on a real hot path before changing anything.

for a principal

The judgment here is the API shape, not the syntax: whether a package hands out T or *T is very hard to change once other teams depend on it, so decide it deliberately on mutation and copy semantics rather than habit.

## Taking the address of a literal In most expressions Go's `&` operator requires an addressable operand: a variable, a pointer dereference, a slice element, a field of an addressable struct. A composite literal is a specific, spec-blessed exception — `&T{...}` is legal for a struct, array, slice or map literal, and it means "allocate a new `T`, initialise it from this literal, and give me its address". So `&Point{X: 1}` is shorthand for: ``` tmp := Point{X: 1} ... &tmp ``` with no name for `tmp`. The initialisation rules are the ordinary composite-literal rules: fields you name get your values, fields you omit get their zero values, so `&Point{}` is a pointer to a fully zeroed `Point`. ## Why constructors look like this Go has no constructors, so a package that wants to hand out an initialised value exposes a function that returns one. When the value must be shared, must be mutated by its methods, or is large enough that copying matters, that function returns a pointer, and its body is almost always a single `&T{...}` expression: ``` func NewEncoder(w io.Writer) *Encoder { return &Encoder{w: w} } ``` The alternative spellings all read worse. `new(Encoder)` returns a `*Encoder` but cannot set `w`, so you would need a second statement. A named temporary plus `&tmp` adds a line and a name that carries no information. ## The & does not decide where it lives A very common misreading is that `&` means "heap". It does not. Go's compiler runs escape analysis: if it can prove the value's lifetime ends with the enclosing function's frame, it allocates it on the stack even though you took its address; if the pointer escapes — returned, stored in a longer-lived structure, captured by a goroutine, passed to something the compiler cannot see through — it is heap-allocated. `go build -gcflags=-m` prints the decision per site, in lines such as `&Point{} escapes to heap` or `&Point{} does not escape`. The practical consequence is that a helper that builds `&Point{...}` and only reads it locally can be allocation-free, and that you should measure rather than assume. ## Elision inside an outer literal Inside a composite literal for a slice, array or map, an element that is itself a composite literal may drop the repeated type. Two forms exist and they are easy to conflate: * When the element type is `T`, the inner `T` is elided: `[]Point{{X: 1}, {X: 2}}`. * When the element type is `*T`, the whole `&T` is elided: `[]*Point{{X: 1}, {X: 2}}`. The second is the one that surprises readers, because there is no visible `&` anywhere and yet each element is a distinct pointer to a separately initialised `Point`. The same elision works for map values, so `map[string]*Point{"origin": {}}` is a map to a pointer to a zeroed `Point`. Nothing is shared between the elements: each inner literal allocates its own value. ## Value or pointer? Deciding between returning `T` and `*T` is an API question that outlives the syntax, but the composite-literal forms make each cheap to write. Reach for `&T{...}` when callers must observe mutations made through the value's methods, when the type carries something that must not be copied, or when the value is genuinely large. Reach for `T{...}` when the value is small and behaves like data, since a value costs no indirection and cannot be nil at the call site. ## Checking what you built When a construction site is suspect, print it. `fmt.Printf("%+v\n", p)` on a `*Point` prints `&{X:1 Y:0}` — the `&` prefix tells you it is a pointer, the field names come from `%+v`, and the zeros show which fields your literal did not set. `%#v` goes further and prints the Go-syntax form, `&main.Point{X:1, Y:0}`, which you can paste back into code. Both are the fastest way to confirm that an elided literal inside a bigger structure really initialised the fields you assumed it did. ## Relationship to new `new(T)` and `&T{}` produce the same thing: a pointer to a zeroed `T`. `&T{...}` additionally initialises fields. Modern Go code therefore uses `&T{...}` for structs and keeps `new` for the handful of cases where there is no useful literal — `new(int)`, for instance, when an API wants a `*int` and you have no variable to point at.

  • In `[]*Point{{X: 1}, {X: 2}}`, why is there no ampersand?
    Elision. Inside a composite literal whose element type is `*T`, an element written as a bare literal has its `&T` supplied for you. Each element still allocates its own `Point` and holds a distinct pointer; nothing is shared between them.
  • Does &Point{} always allocate on the heap?
    No. Escape analysis decides at compile time: a value whose pointer never outlives the frame can stay on the stack despite the `&`. Run `go build -gcflags=-m` to see the per-site verdict, printed as either escaping to the heap or not escaping.
  • How does &Point{X: 1} differ from new(Point)?
    Both return a `*Point` to a value that starts out zeroed, and both allocate. The literal form can set fields in the same expression, which `new` cannot, so `&T{...}` is the idiomatic way to build a struct pointer and `new` is mostly reserved for pointers to plain zero values like `new(int)`.

saying these in an interview costs you the question

  • Says you cannot take the address of a literal in Go
  • Claims &T{} always forces a heap allocation
  • Thinks &T{} copies the struct after allocating it
  • Believes elements of []*T{{}, {}} share one value
  • Says fields omitted from &T{} are left uninitialised