What are named result parameters in a Go function signature, and what do they hold on entry?
answer
- the signature declares them for you
- already zero before line one
- explicit returns still work
- a return with no operands still returns something
- strings.Cut names before, after, found
basics
~20 sNaming results, as in func copyInto(dst, src []byte) (n int, truncated bool), declares them as local variables already set to their zero values. Explicit returns still work; a bare return ships whatever they currently hold.
solid answer
~50 sA result list may give each result a name: `func copyInto(dst, src []byte) (n int, truncated bool)`. Those names are real local variables, declared and initialised to their zero values — `0` and `false` here — before the first statement runs, and you can assign to them, read them, and take their address like any other local. Naming changes nothing about how you return: `return n, truncated` and `return 0, false` are both still legal and both set the named results. What naming *adds* is a bare `return` with no operands, which returns the current values of the named results, and documentation — `strings.Cut` declares `(before, after string, found bool)` so the signature itself says which string is which. Within one result list the names must be all present or all absent, though `_` counts as a name.
code
go · 11 lines// n and truncated exist, as 0 and false, before the first statement runs.
func copyInto(dst, src []byte) (n int, truncated bool) {
n = copy(dst, src)
truncated = n < len(src)
return // returns the current n and truncated
}
func copyInto2(dst, src []byte) (n int, truncated bool) {
n = copy(dst, src)
return n, n < len(src) // equally legal, and explicit
}go deeper
Recall that results can be given names in the signature and that those names are variables set to their zero values before the body runs. Know that you can still write an explicit return.
Explain the mechanics: the names declare outermost-scope locals, an explicit return assigns them, a bare return ships their current values, and names must be all present or all absent in one list.
Argue for naming as documentation — same-typed adjacent results, or a number that needs a unit — and show you know the risk that an inner short declaration can shadow a named result so the function returns an untouched value.
Own it as a house convention: which signatures in a package's exported surface must name results so the generated documentation is readable, and where names would only add noise.
## What naming a result actually does A Go result list can be written two ways: ```go func copyInto(dst, src []byte) (int, bool) // unnamed func copyInto(dst, src []byte) (n int, truncated bool) // named ``` The second form does something concrete rather than cosmetic: it **declares two local variables**, `n` and `truncated`, in the function's outermost scope. They exist before the first statement of the body runs, and like every declared variable in Go they start at their type's zero value — `0` for an `int`, `false` for a `bool`, `""` for a string, `nil` for a slice, map, pointer, channel, function or interface, and an all-zero struct for a struct type. There is no such thing as an uninitialised variable in Go, and named results are no exception. From there they behave like any other local. You may assign to them, read them, pass them to other functions, take their address, and shadow them in an inner block (which is a classic source of confusion, and a reason to keep the names short and distinctive). ## Returning is unchanged — plus one new option The most common misconception is that naming your results forces you to use a bare `return`. It does not. All of these are legal in a function declared `(n int, truncated bool)`: ```go return 0, false // explicit values return n, truncated // explicit, naming the variables return // bare: returns whatever n and truncated hold right now ``` An explicit `return a, b` assigns `a` and `b` to the named results and then returns, so the two forms are equivalent in effect. The bare form — often called a naked return — is only available when *all* results are named, and it is what the names buy you mechanically. The consequence to internalise is that with a bare `return`, the values you send back are a function of the program state at that exact line. If a branch returns before assigning `truncated`, the caller receives `false` — a perfectly plausible-looking answer that no line of code ever decided on. That is why a bare `return` is comfortable in a five-line function and a hazard in a long one. ## Naming as documentation The reason much of the standard library names results has nothing to do with bare returns. Compare: ```go func Cut(s, sep string) (string, string, bool) func Cut(s, sep string) (before, after string, found bool) // the real strings.Cut ``` Only the second tells you, from the signature alone, which of the two strings is the part before the separator. The same motive gives you `io.Reader`'s `Read(p []byte) (n int, err error)` — `n` is the number of bytes read, which an unnamed `(int, error)` leaves you to guess — and `net.SplitHostPort(hostport string) (host, port string, err error)`. Generated documentation shows the signature verbatim, so the names are part of the public description of the function even though callers can never refer to them. A useful review heuristic: name the results when two adjacent results have the same type and could be confused, or when a number needs a unit or a meaning. Leave them unnamed when the types already say everything — `(int, error)` on a function called `Atoi` needs no help. ## The rules worth remembering - Within one result list, names are all present or all absent. You cannot write `(n int, bool)`. If you want to name only some of them, name the rest `_`: `(n int, _ error)` is legal. - Naming results is independent of naming parameters; the two lists are separate. - A named result is *not* an out-parameter. The caller passes nothing in and cannot see the variable; it is purely internal machinery whose final value is copied out at return. - Because the names are ordinary variables, an inner `:=` can declare a *different* variable with the same name, and later assignments go to that one instead. Nothing in the compiler flags it, and the function will keep returning the outer variable's untouched value. ## What interviewers listen for At this level the expected answer is: the names declare zero-valued locals, `return x, y` still works, a bare `return` sends their current values, and the strongest reason to name them is that the signature becomes self-explanatory. A candidate who says "named results are initialised when you first assign them" has missed the zero-value guarantee, and one who says "naming forces naked returns" has inverted the relationship between the two.
- Does naming a Go function's results force you to use a bare return?No. `return a, b` remains legal and assigns the named results before returning. Naming only makes the operand-less form available; plenty of standard-library functions name their results purely so the signature documents itself and never use a bare return at all.
- Can you name only some of the results in a Go result list?Not with real names — within one list the names must be all present or all absent, so `(n int, bool)` is rejected. You can, however, use the blank identifier as a name: `(n int, _ error)` compiles and leaves the second result nameless in practice while satisfying the rule.
- Is a named result an out-parameter that the caller passes in?No. The caller supplies nothing and cannot observe the variable; it is a local of the function, created at entry and copied to the caller when the function returns. The name exists for the body and for the documentation, not for the call site.
- Why does io.Reader's method declare `Read(p []byte) (n int, err error)` rather than `(int, error)`?Because `n` needs a meaning. The signature alone tells you the int is a count of bytes read into `p`, which matters enormously given that a Read may return fewer bytes than the buffer holds. Naming results earns its keep exactly when a bare type leaves the reader guessing.
Naming results is like the function declaring its variables in the doorway instead of the first line of the body: they are already there, already zeroed, and visible to anyone reading the sign on the door.
saying these in an interview costs you the question
- Thinks named results are uninitialised until first assigned
- Claims naming results forces naked returns
- Calls a named result an out-parameter the caller passes in
- Believes the caller can refer to the result names
- Tries to name only one result in a multi-result list