Go の Short variable declarations と Named return values
go-mp4
という、mp4ファイルをパースしてくれるGoで書かれたライブラリがあった。
Goに慣れるため、golint
のカバレッジを上げるPRを出してみた。
自分の書いたコードで、:=
では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)
に対する再宣言が原因でエラーが出ているわけではない。
以下のコードがコンパイルエラーを起こさないことがそれを証明している。
(本来dst
はIBox
という型だが、ここでは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 によると、n
とerror
は関数の最初で定義した変数名として扱われるということだった。
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.
と書いてあった。