Named Return Values
Naming a function's results documents them and lets a deferred block change what the caller receives.
func split(sum int) (x, y int)You can give a function's results names, in the signature itself. func split(sum int) (x, y int) declares two int results called x and y. Those names behave like variables declared at the top of the function body, already holding their zero values: 0 for a number, nil for an error.
Because the results already exist as variables, a bare return with nothing after it is legal. It hands back whatever the named results hold at that moment. It is called a naked return, and it is the shorthand for writing return x, y. The two are the same statement.
Where they earn their place
Two reasons, and both are narrow. The first is documentation: (width, height int) tells a reader which number is which, where (int, int) does not. The second matters more. A deferred function can read and assign a named result, which is the only way to change what a function hands back after its return has already run. That is how one block adds context to every failure path, and how a recovered panic turns into an error.
What trips people up
Keep naked returns to short functions. In a long one the reader has to hunt upwards for the last assignment to work out what comes back. Worse is shadowing: inside an if block, err := ... declares a second variable and leaves the named result untouched, so the function reports success after a real failure. Use = there, not :=.
Example
package main
import (
"fmt"
"strconv"
)
// The results are named n and err. Both start at their zero values,
// 0 and nil, and a bare "return" sends back whatever they hold.
func parseTotal(a, b string) (n int, err error) {
x, err := strconv.Atoi(a)
if err != nil {
return 0, err // the explicit form, same statement
}
y, err := strconv.Atoi(b)
if err != nil {
return 0, err
}
n = x + y
return // identical to "return n, err"
}
func main() {
n, err := parseTotal("20", "22")
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println("total:", n)
}
// Output:
// total: 42When to use it
- Document a function returning two values of the same type, such as (width, height int).
- Add the same context to every error a function can return, from one deferred block.
- Record how a function finished, for a log line or a metric, without editing every return statement.
More examples
One deferred block labels every failure
A function with eight failure paths would otherwise need the same wrapping written eight times, and someone will forget one. Name the error result, wrap it once in a defer, and every path out of the function gains the same context. The nil case passes through untouched, which is why the test inside the deferred literal is there.
package main
import (
"errors"
"fmt"
)
// err is a named result, so the deferred function literal can see it
// and replace it. %v inserts the error's message; a later lesson covers
// %w, which keeps the original error available for inspection.
func loadConfig(path string) (err error) {
defer func() {
if err != nil {
err = fmt.Errorf("loadConfig %q: %v", path, err)
}
}()
if path == "" {
return errors.New("empty path")
}
return nil
}
func main() {
fmt.Println(loadConfig(""))
fmt.Println(loadConfig("app.toml"))
}
// Output:
// loadConfig "": empty path
// <nil>The shadowing bug, and the fix
This is the one failure mode of named results worth memorising, because it compiles cleanly and the function looks right. The inner err is a different variable that goes out of scope at the closing brace, so the naked return still sends back nil. Keep it in mind whenever you write := in a block inside a function with named results.
package main
import "fmt"
// Broken: the named result err is never set.
func broken(bad bool) (err error) {
if bad {
// := declares a NEW err that belongs to this if block only.
err := fmt.Errorf("it failed")
_ = err
}
return
}
// Fixed: = assigns to the named result itself.
func fixed(bad bool) (err error) {
if bad {
err = fmt.Errorf("it failed")
}
return
}
func main() {
fmt.Println(broken(true))
fmt.Println(fixed(true))
}
// Output:
// <nil>
// it failed
Discussion