Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 18761] ファイルの概要

このコミットは、Go言語の仕様書(doc/go_spec.html)に、シャドーイングされた戻り値パラメータに関する実装上の制限を追記するものです。具体的には、名前付き戻り値パラメータがシャドーイングされている場合に、空のreturn文を許可しないというコンパイラの挙動を明文化しています。これは言語仕様の変更ではなく、既存のコンパイラ(gcおよびgccgo)の挙動を文書化したものです。

コミット

commit c97778f4302bd0e39045c931941e32f178493f45
Author: Robert Griesemer <gri@golang.org>
Date:   Wed Mar 5 11:59:53 2014 -0800

    spec: shadowed return parameters may be disallowed
    
    This documents the implemented behavior of both
    gc and gccgo as an implementation restriction.
    
    NOT A LANGUAGE CHANGE.
    
    Fixes #5425.
    
    LGTM=rsc, r, iant
    R=r, iant, rsc, ken
    CC=golang-codereviews
    https://golang.org/cl/71430043

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/c97778f4302bd0e39045c931941e32f178493f45

元コミット内容

spec: shadowed return parameters may be disallowed

This documents the implemented behavior of both
gc and gccgo as an implementation restriction.

NOT A LANGUAGE CHANGE.

Fixes #5425.

変更の背景

Go言語では、関数が名前付き戻り値パラメータを持つことができます。これらのパラメータは、関数の冒頭でゼロ値で初期化され、return文で明示的に値を指定しない場合、その時点での値が返されます。しかし、関数内部でこれらの名前付き戻り値パラメータと同じ名前の新しい変数を:=(短い変数宣言)を使って宣言すると、その新しい変数が元の戻り値パラメータを「シャドーイング(shadowing)」してしまいます。

シャドーイングされた変数は、そのスコープ内では元の変数とは異なる独立した存在となります。このため、シャドーイングされた変数に値を代入しても、元の名前付き戻り値パラメータの値は変更されません。特に、エラーハンドリングで頻繁に用いられるerr変数がシャドーイングされると、開発者が意図しない挙動(例えば、関数が常にnilエラーを返すなど)を引き起こす可能性がありました。

この問題は、Goコミュニティで認識されており、Issue #5425として報告されていました。このコミットは、このような混乱を避けるため、コンパイラがシャドーイングされた戻り値パラメータが存在するスコープで空のreturn文(returnのみで戻り値を指定しない形式)を許可しないという、既存のコンパイラ(gcおよびgccgo)の挙動をGo言語の仕様書に明文化することを目的としています。これにより、開発者はシャドーイングによる潜在的な問題をより明確に認識し、適切なコーディングスタイルを適用するよう促されます。

前提知識の解説

1. Go言語の名前付き戻り値パラメータ (Named Return Parameters)

Go言語の関数は、戻り値に名前を付けることができます。例えば、func myFunc() (result int, err error)のように宣言すると、resulterrがその関数の戻り値パラメータとして定義されます。これらのパラメータは、関数の本体に入ると自動的にその型のゼロ値(intなら0errorならnil)で初期化されます。関数内でreturn文が実行される際、明示的に戻り値を指定しないreturn(裸のreturn)を使用すると、これらの名前付き戻り値パラメータの現在の値がそのまま関数の戻り値となります。

func divide(a, b int) (quotient int, err error) {
    if b == 0 {
        err = errors.New("division by zero") // err は名前付き戻り値パラメータ
        return // err の現在の値 (errors.New("division by zero")) が返される
    }
    quotient = a / b // quotient は名前付き戻り値パラメータ
    return // quotient の現在の値 (a/b) が返される
}

2. 変数のシャドーイング (Variable Shadowing)

シャドーイングとは、内側のスコープで宣言された変数が、外側のスコープで同じ名前を持つ変数を「隠す」現象を指します。Go言語では、特に短い変数宣言:=を使用する際にシャドーイングが発生しやすいです。

package main

import "fmt"

func main() {
    x := 10 // 外側のスコープの x

    if true {
        x := 20 // 内側のスコープの x。外側の x をシャドーイング
        fmt.Println(x) // 20 を出力
    }

    fmt.Println(x) // 10 を出力
}

この例では、ifブロック内でx := 20とすることで、新しいx変数が宣言され、外側のxをシャドーイングしています。ifブロック内のxへの変更は、外側のxには影響しません。

3. :== の違い

  • := (短い変数宣言): 変数の宣言と初期化を同時に行います。左辺に少なくとも1つの新しい変数が含まれている必要があります。新しい変数を宣言するため、シャドーイングを引き起こす可能性があります。
  • = (代入): 既に宣言されている変数に値を代入します。新しい変数を宣言することはありません。

シャドーイングの問題は、名前付き戻り値パラメータと同じ名前の変数を:=で宣言してしまうことで発生します。これにより、開発者が意図していた名前付き戻り値パラメータではなく、シャドーイングされた新しいローカル変数に値が代入されてしまい、裸のreturn文が期待通りの値を返さないというバグにつながることがあります。

技術的詳細

このコミットは、Go言語の仕様書(doc/go_spec.html)の「Return statements」セクションに新しい段落を追加することで、シャドーイングされた戻り値パラメータに関するコンパイラの実装上の制限を明文化しています。

追加された内容は以下の通りです。

Implementation restriction: A compiler may disallow an empty expression list in a "return" statement if a different entity (constant, type, or variable) with the same name as a result parameter is in scope at the place of the return.

これは、「コンパイラは、戻り値パラメータと同じ名前の異なるエンティティ(定数、型、または変数)がreturn文の場所でスコープ内にある場合、空の式リストを持つreturn文を許可しないことがある」という意味です。

重要な点は、これが「Implementation restriction(実装上の制限)」であり、「NOT A LANGUAGE CHANGE(言語の変更ではない)」と明記されていることです。これは、Go言語の文法やセマンティクス自体を変更するものではなく、既存のGoコンパイラ(gcやgccgoなど)が既にこの挙動を実装していることを仕様書に追記し、その挙動を正式に文書化したものです。

この制限の目的は、前述のシャドーイングによる混乱やバグを防ぐことにあります。特に、名前付き戻り値パラメータがシャドーイングされている状況で、開発者が意図せず裸のreturn文を使用してしまい、期待と異なる値が返されることを防ぐためのガードレールとして機能します。コンパイラがこのようなケースをエラーとして扱うことで、開発者は早期に問題を検出し、=による明示的な代入を使用するなど、正しい方法でコードを修正するよう促されます。

コミットに含まれる例は、この制限が適用される典型的なシナリオを示しています。

func f(n int) (res int, err error) {
    if _, err := f(n-1); err != nil {
        return  // invalid return statement: err is shadowed
    }
    return
}

この例では、f関数はreserrという名前付き戻り値パラメータを持っています。if文の条件部で_, err := f(n-1)という短い変数宣言が使われています。ここで宣言されたerrは、関数の戻り値パラメータであるerrをシャドーイングしています。このシャドーイングされたerrnilでない場合にreturn(裸のreturn)が実行されると、コンパイラはこのreturn文を無効と判断します。なぜなら、このreturn文の場所では、名前付き戻り値パラメータのerrがシャドーイングされており、裸のreturnがどのerrを返すのか曖昧になるためです。

この変更は、Go言語の堅牢性を高め、開発者がより安全で予測可能なコードを書くのを助けるためのものです。

コアとなるコードの変更箇所

変更はdoc/go_spec.htmlファイルに対して行われています。

--- a/doc/go_spec.html
+++ b/doc/go_spec.html
@@ -1,6 +1,6 @@
 <!--{
  	"Title": "The Go Programming Language Specification",
-	"Subtitle": "Version of March 4, 2014",
+	"Subtitle": "Version of March 5, 2014",
  	"Path": "/ref/spec"
 }-->
 
@@ -5002,6 +5002,21 @@ function. A "return" statement that specifies results sets the result parameters
 any deferred functions are executed.
 </p>
 
+<p>
+Implementation restriction: A compiler may disallow an empty expression list
+in a "return" statement if a different entity (constant, type, or variable)
+with the same name as a result parameter is in
+<a href="#Declarations_and_scope">scope</a> at the place of the return.
+</p>
+
+<pre>
+func f(n int) (res int, err error) {
+\tif _, err := f(n-1); err != nil {\n+\t\treturn  // invalid return statement: err is shadowed
+\t}\n+\treturn
+}\n+</pre>
+
 
 <h3 id="Break_statements">Break statements</h3>
 

コアとなるコードの解説

このコミットの主要な変更は、doc/go_spec.htmlに新しい<p>タグと<pre>タグを追加したことです。

  1. <p>タグで囲まれた説明文の追加:

    <p>
    Implementation restriction: A compiler may disallow an empty expression list
    in a "return" statement if a different entity (constant, type, or variable)
    with the same name as a result parameter is in
    <a href="#Declarations_and_scope">scope</a> at the place of the return.
    </p>
    

    この段落は、Goコンパイラがどのようにシャドーイングされた戻り値パラメータを扱うかについて、実装上の制限を記述しています。具体的には、戻り値パラメータと同じ名前の別のエンティティ(定数、型、または変数)がreturn文のスコープ内に存在する場合、コンパイラは空の式リストを持つreturn文(つまり、裸のreturn)を許可しない可能性があることを述べています。これは、コンパイラがそのような状況をエラーとして扱うことを意味し、開発者にシャドーイングの問題を認識させ、修正を促します。

  2. <pre>タグで囲まれたコード例の追加:

    <pre>
    func f(n int) (res int, err error) {
    	if _, err := f(n-1); err != nil {
    		return  // invalid return statement: err is shadowed
    	}
    	return
    }
    </pre>
    

    このコード例は、上記の制限が適用される具体的なシナリオを示しています。f関数はreserrという名前付き戻り値パラメータを持っています。if _, err := f(n-1);の行では、新しいerr変数が宣言され、関数の名前付き戻り値パラメータであるerrをシャドーイングしています。このシャドーイングされたerrnilでない場合に実行されるreturn文は、コメントで示されているように「invalid return statement: err is shadowed」と見なされます。これは、裸のreturnがどのerr(シャドーイングされたローカル変数か、名前付き戻り値パラメータか)を参照すべきか曖昧になるため、コンパイラがエラーを発生させることを意味します。

これらの変更は、Go言語の仕様書をより正確かつ詳細にし、開発者がシャドーイングによる潜在的な落とし穴を理解し、回避するための重要な情報を提供します。

関連リンク

参考にした情報源リンク

  • Stack Overflow: Go variable shadowing and named return values (https://stackoverflow.com/questions/tagged/go-variable-shadowing)
  • Reddit: Go variable shadowing discussions (https://www.reddit.com/r/golang/comments/...)
  • rpeshkov.net: Understanding Go's variable shadowing (https://rpeshkov.net/go-variable-shadowing/)
  • Go Language Specification: Return statements (https://go.dev/ref/spec#Return_statements) (コミットが適用された後の最新版の仕様書)