経験は何よりも饒舌

10年後に真価を発揮するかもしれないブログ 

Go の Short variable declarations と Named return values


go-mp4という、mp4ファイルをパースしてくれるGoで書かれたライブラリがあった。
Goに慣れるため、golintカバレッジを上げるPRを出してみた。

github.com

自分の書いたコードで、:=ではno new variables on left side of :=というエラーが出たけれど、代わりに=を使えばうまくいく、という事象があった。
具体的には、以下の部分でエラーが出ていた。

func UnmarshalAny(r io.ReadSeeker, boxType BoxType, payloadSize uint64, ctx Context) (box IBox, n uint64, err error) {
	dst, err := boxType.New(ctx)
	if err != nil {
		return nil, 0, err
	}
	n, err := Unmarshal(r, payloadSize, dst, ctx) // no new variables on left side of :=
	return dst, n, err
}

Goの言語仕様のShort_variable_declarationsによると、同じブロック内で同じ型として宣言された変数に対して、宣言する変数のうち少なくともひとつはブランク変数でなければ、再宣言が可能ということだった。

Unlike regular variable declarations, a short variable declaration may redeclare variables provided they were originally declared earlier in the same block (or the parameter lists if the block is the function body) with the same type, and at least one of the non-blank variables is new.

ただし今回は、dst, err := boxType.New(ctx)に対する再宣言が原因でエラーが出ているわけではない。
以下のコードがコンパイルエラーを起こさないことがそれを証明している。
(本来dstIBoxという型だが、ここではstringにしている。)

func main() {
	dst, err := "foo", fmt.Errorf("error1")
	n, err := 1, fmt.Errorf("error2")
	
	fmt.Println(dst,n,err) // foo 1 error2
}

試しに以下のように、関数内で使われているnという変数名をn1に変えてみると、no new variables on left side of :=というエラーは起こらなかった。

func UnmarshalAny(r io.ReadSeeker, boxType BoxType, payloadSize uint64, ctx Context) (box IBox, n uint64, err error) {
	dst, err := boxType.New(ctx)
	if err != nil {
		return nil, 0, err
	}
        // n1に変えた
	n1, err := Unmarshal(r, payloadSize, dst, ctx)
	return dst, n1, err
}

また、戻り値の変数の名前(named return value)をnからn1に変えてみても、no new variables on left side of :=というエラーは起こらなかった。

func UnmarshalAny(r io.ReadSeeker, boxType BoxType, payloadSize uint64, ctx Context) (box IBox, n1 uint64, err error) { // n1に変えた
	dst, err := boxType.New(ctx)
	if err != nil {
		return nil, 0, err
	}
	n, err := Unmarshal(r, payloadSize, dst, ctx)
	return dst, n, err
}

A Tour of Go の Named return values によると、nerrorは関数の最初で定義した変数名として扱われるということだった。

Goでの戻り値となる変数に名前をつける( named return value )ことができます。戻り値に名前をつけると、関数の最初で定義した変数名として扱われます。
この戻り値の名前は、戻り値の意味を示す名前とすることで、関数のドキュメントとして表現するようにしましょう。

つまり、以下のコードが通らず、

func main() {
        var n uint64
        var err error
	
	n, err := 1, fmt.Errorf("error") // no new variables on left side of :=
}

以下のコードが通る、ということが今回起きていたのだった。

func main() {
        var n uint64
        var err error
	
	n, err = 1, fmt.Errorf("error")
}


Goの言語仕様でnamed return valueの仕様は、さっとみた限りヒットしなかったが、Defer statementsの欄に、

function has named result parameters that are in scope within the literal, the deferred function may access and modify the result parameters before they are returned.

と書いてあった。