## 5.3. 多返迴值

在Go中，一個函數可以返迴多個值。我們已經在之前例子中看到，許多標準庫中的函數返迴2個值，一個是期望得到的返迴值，另一個是函數出錯時的錯誤信息。下面的例子會展示如何編寫多返迴值的函數。

下面的程序是findlinks的改進版本。脩改後的findlinks可以自己發起HTTP請求，這樣我們就不必再運行fetch。因爲HTTP請求和解析操作可能會失敗，因此findlinks聲明了2個返迴值：鏈接列表和錯誤信息。一般而言，HTML的解析器可以處理HTML頁面的錯誤結點，構造出HTML頁面結構，所以解析HTML很少失敗。這意味着如果findlinks函數失敗了，很可能是由於I/O的錯誤導致的。

```Go
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中，有4處return語句，每一處return都返迴了一組值。前三處return，將http和html包中的錯誤信息傳遞給findlinks的調用者。第一處return直接返迴錯誤信息，其他兩處通過fmt.Errorf（§7.8）輸出詳細的錯誤信息。如果findlinks成功結束，最後的return語句將一組解析獲得的連接返迴給用戶。

在finallinks中，我們必鬚確保resp.Body被關閉，釋放網絡資源。雖然Go的垃圾迴收機製會迴收不被使用的內存，但是這不包括操作繫統層面的資源，比如打開的文件、網絡連接。因此我們必鬚顯式的釋放這些資源。

調用多返迴值函數時，返迴給調用者的是一組值，調用者必鬚顯式的將這些值分配給變量:

```Go
links, err := findLinks(url)
```

如果某個值不被使用，可以將其分配給blank identifier:

```Go
links, _ := findLinks(url) // errors ignored
```

一個函數內部可以將另一個有多返迴值的函數作爲返迴值，下面的例子展示了與findLinks有相同功能的函數，兩者的區别在於下面的例子先輸出參數：

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

當你調用接受多參數的函數時，可以將一個返迴多參數的函數作爲該函數的參數。雖然這很少出現在實際生産代碼中，但這個特性在debug時很方便，我們隻需要一條語句就可以輸出所有的返迴值。下面的代碼是等價的：

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

準確的變量名可以傳達函數返迴值的含義。尤其在返迴值的類型都相同時，就像下面這樣：

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

雖然良好的命名很重要，但你也不必爲每一個返迴值都取一個適當的名字。比如，按照慣例，函數的最後一個bool類型的返迴值表示函數是否運行成功，error類型的返迴值代表函數的錯誤信息，對於這些類似的慣例，我們不必思考合適的命名，它們都無需解釋。

如果一個函數將所有的返迴值都顯示的變量名，那麽該函數的return語句可以省略操作數。這稱之爲bare return。

```Go
// 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) { /* ... */ }
```

按照返迴值列表的次序，返迴所有的返迴值，在上面的例子中，每一個return語句等價於：

```Go
return words, images, err
```

當一個函數有多處return語句以及許多返迴值時，bare return 可以減少代碼的重複，但是使得代碼難以被理解。舉個例子，如果你沒有仔細的審査代碼，很難發現前2處return等價於 return 0,0,err（Go會將返迴值 words和images在函數體的開始處，根據它們的類型，將其初始化爲0），最後一處return等價於 return words，image，nil。基於以上原因，不宜過度使用bare return。

**練習 5.5：** 實現countWordsAndImages。（參考練習4.9如何分詞）

**練習 5.6：** 脩改gopl.io/ch3/surface (§3.2) 中的corner函數，將返迴值命名，併使用bare return。
