Go Language Core Beauty 4.3 – Multiple Return Values

In the Go language, a function can have multiple return values, a feature we have seen a lot in the previous examples, many standard library functions will return two values, one is the expected result of the function execution, and the other is the wrong value when the function errors.

The following program is an improved version of findlinks that can initiate HTTP requests on its own so that you don’t have to run fetch anymore. Because both HTTP requests and parsing operations can fail, findlinks declare two return values: a list of stored links and an error value. In general, html parsers construct the wrong HTML node when they encounter an error, so parsing HTML rarely fails, and once it fails, it is likely to be caused by IO errors.

gopl.io/ch5/findlinks2

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
}

findlinks has 4 return statements, each of which returns a pair of values. The first three returns the error messages encountered during execution, the first one returns the error message directly, and the next two return the error messages through fmt. Errorf outputs more detailed error information. If findlinks end successfully, the final return statement returns the list of links obtained by the resolution to the user, and the error message is nil.

In finallinks, we have to make sure that the resp. The Body is shut down and then frees up network resources, even when an error occurs. Although Go’s garbage collection mechanism will reclaim memory that is no longer used, don’t assume that unused operating system resources will also be recycled, such as open files, network connections, etc., we should make explicit recycling!

a multi-return function returns a set of values to the caller, so the caller must explicitly assign these values to the appropriate variables (if they will be used later):

links, err := findLinks(url)

if a value is not used, it can be assigned to a blank identifier:

links, _ := findLinks(url) // This means ignore the returned error

a function can take another function that has multiple return values as the return value:

func findLinksLog(url string) ([]string, error) {
    log.Printf("findLinks %s", url)
    return findLinks(url)
}

this feature is convenient for debug, and we only need one statement to output all the return values. the following code is equivalent:

log.Println(findLinks(url))
links, err := findLinks(url)
log.Println(links, err)

you can use a named return value to assign an explicit meaning to a return value, especially if the return value is of the same type, as follows:

func Size(rect image.Rectangle) (width, height int)
func Split(path string) (dir, file string)
func HourMinSec(t time.Time) (hour, minute, second int)

while good naming is important, you don’t have to give every return value an appropriate name. for example, by convention, the return value of the last bool type of a function indicates whether the function ran successfully, and the return value of type error represents the error message of the function, and we do not have to think about the appropriate naming for these similar conventions.

if all the return values of a function are named, then the function’s return statement can omit the operand, which is called 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) { /* ... */ }

here all return values will be returned in the order of the return value list, in the example above, each return statement is equivalent to:

return words, images, err

when a function has multiple return statements and many return values, the naked return can reduce code duplication, but it can make our code difficult to understand. for example, if you don’t examine the code carefully, it’s hard to find that the first 2 returns are equivalent to return 0,0,err (the named return value of the function is initialized to zero of the corresponding type by default), and the last one is equivalent to return words, image, nil. for the above reasons, naked return should not be used excessively.

Exercise 5.5: Implementing countWordsAndImages. (Refer to Exercise 4.9 How to Part Words)

exercise 5.6: modify the cornerstone function in gopl.io/ch3/surface (§3.2) to name the return value and use bar return.

Leave a Reply