A function can return more than one result. We’ve seen many examples of functions from standard packages that return two values, the desired computational result and an error value or boolean that indicates whether the computation worked. The next example shows how to write one of our own.
The program below is a variation of findlinks
that makes the
HTTP request itself so that we no longer need to run fetch
.
Because the HTTP and parsing operations can fail,
findLinks
declares two results: the list of discovered links
and an error.
Incidentally, the HTML parser can usually recover from bad input and
construct a document containing error nodes, so Parse
rarely
fails; when it does, it’s typically due to underlying I/O errors.
func main() { for _, url := range os.Args[1:] { links, err := findLinks(url) if err != nil { fmt.Fprintf(os.Stderr, "findlinks2: %v\n", err) continue } for _, link := range links { fmt.Println(link) } } } // findLinks performs an HTTP GET request for url, parses the // response as HTML, and extracts and returns the links. func findLinks(url string) ([]string, error) { resp, err := http.Get(url) if err != nil { return nil, err } if resp.StatusCode != http.StatusOK { resp.Body.Close() return nil, fmt.Errorf("getting %s: %s", url, resp.Status) } doc, err := html.Parse(resp.Body) resp.Body.Close() if err != nil { return nil, fmt.Errorf("parsing %s as HTML: %v", url, err) } return visit(nil, doc), nil }
There are four return statements in findLinks
, each of which
returns a pair of values.
The first three return
s cause the function to pass the
underlying errors from the http
and html
packages on to
the caller.
In the first case, the error is returned unchanged; in the second and third, it
is augmented with additional context information by fmt.Errorf
(§7.8).
If findLinks
is successful, the final return statement
returns the slice of links, with no error.
We must ensure that resp.Body
is closed
so that network resources are properly released
even in case of error.
Go’s garbage collector recycles unused memory, but do not assume it
will release unused operating system resources like open files and
network connections. They should be closed explicitly.
The result of calling a multi-valued function is a tuple of values. The caller of such a function must explicitly assign the values to variables if any of them are to be used:
links, err := findLinks(url)
To ignore one of the values, assign it to the blank identifier:
links, _ := findLinks(url) // errors ignored
The result of a multi-valued call may itself be returned from a
(multi-valued) calling function, as in this function that behaves
like findLinks
but logs its argument:
func findLinksLog(url string) ([]string, error) { log.Printf("findLinks %s", url) return findLinks(url) }
A multi-valued call may appear as the sole argument when calling a function of multiple parameters. Although rarely used in production code, this feature is sometimes convenient during debugging since it lets us print all the results of a call using a single statement. The two print statements below have the same effect.
log.Println(findLinks(url)) links, err := findLinks(url) log.Println(links, err)
Well-chosen names can document the significance of a function’s results. Names are particularly valuable when a function returns multiple results of the same type, like
func Size(rect image.Rectangle) (width, height int) func Split(path string) (dir, file string) func HourMinSec(t time.Time) (hour, minute, second int)
but it’s not always necessary to name multiple results solely for
documentation. For instance, convention dictates that a final bool
result indicates success; an error
result often needs no explanation.
In a function with named results, the operands of a return statement may be omitted. This is called a bare return.
// CountWordsAndImages does an HTTP GET request for the HTML // document url and returns the number of words and images in it. func CountWordsAndImages(url string) (words, images int, err error) { resp, err := http.Get(url) if err != nil { return } doc, err := html.Parse(resp.Body) resp.Body.Close() if err != nil { err = fmt.Errorf("parsing HTML: %s", err) return } words, images = countWordsAndImages(doc) return } func countWordsAndImages(n *html.Node) (words, images int) { /* ... */ }
A bare return is a shorthand way to return each of the named result variables in order, so in the function above, each return statement is equivalent to
return words, images, err
In functions like this one, with many return statements and several
results, bare returns can reduce code duplication, but they rarely
make code easier to understand.
For instance, it’s not obvious at first glance that the two early
returns are equivalent to return 0, 0, err
(because the
result variables words
and images
are initialized to their zero values) and that the final return
is equivalent to return words, images, nil
.
For this reason, bare returns are best used sparingly.
Exercise 5.5:
Implement countWordsAndImages
.
(See Exercise 4.9 for word-splitting.)
Exercise 5.6:
Modify the corner
function in gopl.io/ch3/surface
(§3.2) to use named results and a bare return statement.