Named return values
Every return value you saw in the previous lesson was anonymous: (int, int) tells the compiler the types but leaves the reader guessing which is which. Go also lets you name the return values in the signature, which gives them two useful properties: they document what each value means, and you can assign to them by name in the body.
Adding names to the signature
Names go before their type, the same way parameter names do. When two named returns share a type, you can group them:
func divmod(a, b int) (quotient, remainder int) {
return a / b, a % b
}
Compare that to the anonymous version:
func divmod(a, b int) (int, int) // anonymous
func divmod(a, b int) (quotient, remainder int) // named
Both signatures describe a function that returns two ints, and both compile the same. The named form tells a caller who only sees the signature which return is which, without having to open the body. That is the single biggest reason to use named returns: they double as documentation.
Calling the function is unchanged either way. Names in the signature do not affect how the caller invokes the function or destructures the return values.
Named returns are pre-declared variables
Inside the function body, each named return is a variable that is already in scope, pre-initialised to its type's zero value. You can read it or assign to it without declaring anything:
func divmod(a, b int) (quotient, remainder int) {
quotient = a / b
remainder = a % b
return
}
The bare return at the end uses whatever values the named returns currently hold. This is called a naked return: no values listed after the return keyword, just the keyword itself. It returns the current values of every named return in the signature.
Naked returns stay readable only when the whole body fits in your eye-line. In a function longer than a screen, the reader has to scroll back up to remember what the named returns were called, and the missing values at the return site obscure the control flow. The community rule of thumb: keep named returns for their documentation value, but still write explicit return v1, v2 statements in longer functions. Save naked return for short helpers where the signature and the last line are visible at once.
When named returns pay off
Three situations where named returns earn their keep:
- Short functions where the return meanings are not obvious. A 1-line
splitfunction returning(x, y int)says nothing;(first, second int)says a lot. - Functions with several return values of the same type.
func bounds() (min, max, avg int)is clearer thanfunc bounds() (int, int, int). defer-based patterns where a deferred function needs to modify a return value on the way out. Only named returns allow that.
For everyday functions that return two values with obvious meanings, unnamed returns are perfectly fine and are still the common case in real Go code. Use named returns when they add clarity, not just because you can.
The third case is worth seeing in code, because it is the one named returns change in a way the caller can observe.
This example uses an anonymous function with defer. Anonymous functions get their own lesson later in this chapter. For now, read func() { ... }() as "run this small block later, right before the function returns."
func double() (result int) {
result = 5
defer func() {
result *= 2
}()
return
}
fmt.Println(double()) // 10
Step by step:
resultstarts at its zero value,0.result = 5stores5in the named return variable.deferqueues a function that will doubleresult.returnstarts returning fromdouble.- Before
doubleactually exits, the deferred function runs and changesresultfrom5to10. - The caller receives
10.
If this still feels a bit abstract, that is normal. This example combines named returns with defer, and both features make more sense after you have seen them a few times. The main thing to notice for now is that a named return gives deferred code a return variable it can still change before the function actually finishes.
With an unnamed return, the local variable can still change, but the returned value is already decided:
func doublePlain() int {
result := 5
defer func() {
result *= 2
}()
return result
}
fmt.Println(doublePlain()) // 5
That contrast is the important part. Named returns are what let a deferred function adjust the value that actually comes back to the caller. This shows up in real code when you want to tag an error on the way out or roll back state on a failed operation (covered properly in the Errors chapter).
Write a function stats that takes two int parameters and returns three named values, in this order:
sum, the sum of the twodiff, the first minus the secondproduct, the two multiplied together
Use a naked return at the end (assign to each named return inside the body, then return with nothing after it).
- Call
stats(5, 3)frommain. - Print the three return values on one line with
fmt.Println.
8 2 15