はじめに
Goでは名前付き戻り値(Named Return Value)はあまり使われない。
この記事では、名前付き戻り値の使い方を解説します。
名前付き戻り値とは?
名前付き戻り値を使うと、引数なしの空return文を呼び出せる。
func f(a int) (b int) {
b = a
return
}
上記の場合だと、戻り値はbの値となる。
名前付き戻り値の使用例
非公開のインターフェイスの場合ドキュメントは必須ではない。以下の例は、住所から座標を取得するメソッドを持つインターフェイスである。
type locator interface {
getCoordinates(address string) (float32, float32, error)
}
ドキュメントがないと、float32
の情報だけでは、緯度か経度かが不明瞭である。
そこで以下のように、名前付き戻り値を使用することで、シグニチャを明示する。
type locator interface {
getCoordinates(address string) (lat, lng float32, err error)
}
func (l loc) getCoordinates(address string) (lat, lng float32, err error) {
return 0, 0, nil
}
無意味な名前付き戻り値
error型がerrorであることは自明なので、名前付き戻り値にはしてはいけない。
func StoreCustomer(customer Customer)(err error) {}
可読性以外の名前付き戻り値
可読性を高める以外にも、名前付き戻り値が有効なことがある。
以下は、Effective Goで提案された例である。
func ReadFull(r io.Reader, buf []byte) (n int, err error) {
for len(buf) > 0 && err == nil {
var nr int
nr, err = r.Read(buf)
n += nr
buf = buf[nr:]
}
return
}
n
とerr
は共にゼロ値に初期化sれるので、実装が短くなる。
空return
空returnは一般的に短い関数で用いられる。長い関数で使用すると、可読性が悪くなるので、使用すべきではない。1つの関数内で、空returnを使うか、引数のあるreturnを使用するか一貫性を保つことが大事。
名前付き戻り値の副作用
名前付き戻り値は、ゼロ値で初期化されるので、意図しないバグを招く可能性がある。
以下は、先ほどのgetCoordinates
を拡張し、住所を検証して、座標を取得する。また、途中で、コンテキストで、デッドラインが過ぎてないかやキャンセルされていないかを検査している。
func (l loc) getCoordinates(ctx context.Context, address string) (
lat, lng float32, err error) {
isValid := l.validateAddress(address)
if !isValid {
return 0, 0, errors.New("invalid address")
}
if ctx.Err() != nil {
return 0, 0, err
}
return 0, 0, nil
}
ここで、if ctx.Err() != nil
で返されエラーはerr
ですが、err
には何も代入しておらず、ゼロ値であるnil
しか返さない。
また、err
は名前付き戻り値によって初期化しているので、名前付き戻り値がなければ、コンパイルエラーとなる。
この解決方法は2つある。
1つ目は以下のようにerr
にctx.Err()
を代入する。
if err := ctx.Err(); err != nil {
return 0, 0, err
}
ただし、これはerrがシャドーイングしているので注意する。
2つ目の方法は、空returnを使用する。
if err = ctx.Err(); err != nil {
return
}
これは、空returnの一貫性を保つことに反するので、使ってはいけない。