[インデックス 10912] ファイルの概要
このコミットは、Go言語の公式ドキュメント「Effective Go」に「Redeclaration(再宣言)」に関する新しいセクションを追加するものです。具体的には、:=
(ショート変数宣言)がどのように既存の変数と新しい変数を同時に扱うか、特にエラーハンドリングの文脈でどのように機能するかを詳細に説明しています。
コミット
commit a41006f35a5931387a3111739e4c97ff3568bbcc
Author: Rob Pike <r@golang.org>
Date: Tue Dec 20 14:15:35 2011 -0800
effective_go: redeclaration
Fixes #2455.
Fixes #2013.
R=rsc, r, gri
CC=golang-dev
https://golang.org/cl/5498053
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a41006f35a5931387a3111739e4c97ff3568bbcc
元コミット内容
このコミットは、doc/effective_go.html
および doc/effective_go.tmpl
ファイルに、Go言語のショート変数宣言(:=
)における変数の「再宣言」に関する新しいセクションを追加しています。このセクションは、特にerr
変数の再利用がどのように行われるか、そしてその背後にあるGo言語の設計思想(実用主義)を説明しています。
変更の背景
この変更は、Go言語のショート変数宣言(:=
)の挙動、特に既存の変数と新しい変数が混在する場合の「再宣言」のルールについて、ユーザーが混乱する可能性があったため、公式ドキュメントで明確な説明を提供することを目的としています。コミットメッセージにある Fixes #2455
と Fixes #2013
は、この機能に関する既存の課題や議論があったことを示唆しています。
Go言語では、エラーハンドリングのパターンとして、複数の関数呼び出しで同じerr
変数を再利用することが一般的です。しかし、:=
演算子は通常、新しい変数を宣言するために使用されます。この二つの概念が衝突する可能性があるため、Go言語は特定の条件下で既存の変数を「再宣言」ではなく「再代入」として扱う特別なルールを導入しています。このコミットは、その「特別なルール」を公式ドキュメントに明記することで、開発者の理解を深め、混乱を解消しようとしています。
前提知識の解説
Go言語の変数宣言
Go言語には、変数を宣言する方法がいくつかあります。
-
var
キーワードによる宣言:var name string = "Go" var age int
これは明示的な型指定と初期化を行う方法です。
-
ショート変数宣言(
:=
):name := "Go" // 型推論により string 型となる age := 10 // 型推論により int 型となる
これは関数内でのみ使用でき、型推論が行われるため、より簡潔に記述できます。
Go言語のエラーハンドリング
Go言語では、エラーは戻り値として扱われることが一般的です。慣習として、関数の最後の戻り値はerror
型であることが多く、エラーが発生しなかった場合はnil
が返されます。
func doSomething() (result string, err error) {
// ...
if someCondition {
return "", errors.New("something went wrong")
}
return "success", nil
}
このエラーハンドリングのパターンでは、複数の操作を行う際に同じerr
変数を繰り返し使用することがよくあります。
f, err := os.Open("file.txt")
if err != nil {
return err
}
defer f.Close()
data, err := ioutil.ReadAll(f) // ここでも err を再利用
if err != nil {
return err
}
スコープ
Go言語におけるスコープは、変数がアクセス可能な範囲を定義します。変数は宣言されたブロック({}
で囲まれた範囲)内で有効です。外側のスコープで宣言された変数は、内側のスコープからアクセスできますが、内側のスコープで同じ名前の変数を宣言すると、それは新しい変数となり、外側の変数を「シャドウ」します。
技術的詳細
このコミットで追加された「Redeclaration」セクションは、:=
ショート変数宣言の特別な挙動について説明しています。通常、:=
は新しい変数を宣言しますが、特定の条件下では、既に宣言されている変数を「再代入」しつつ、同時に新しい変数を宣言することができます。
その条件は以下の3つです。
-
同じスコープ内での宣言:
v
という変数が既に宣言されている場合、この:=
宣言がv
の既存の宣言と同じスコープ内にある必要があります。もしv
が外側のスコープで宣言されている場合、:=
宣言は新しいv
変数を(内側のスコープで)作成し、外側のv
をシャドウします。 -
初期化値の代入可能性: 対応する初期化値が、既存の
v
変数に代入可能である必要があります。 -
少なくとも1つの新しい変数の宣言: この
:=
宣言において、少なくとも1つの新しい変数が宣言されている必要があります。つまり、全ての変数が既存のものである場合は、:=
は使用できず、通常の代入(=
)を使用する必要があります。
このルールは、特にGo言語で頻繁に登場するエラーハンドリングのパターンにおいて、err
変数を繰り返し使用する際に非常に実用的です。これにより、冗長なvar err error
の宣言を避けることができ、コードをより簡潔に保つことができます。
例:
f, err := os.Open(name) // f と err を新規宣言
if err != nil {
// エラー処理
}
d, err := f.Stat() // d を新規宣言し、既存の err に再代入
if err != nil {
// エラー処理
}
この挙動は、Go言語の「実用主義(pragmatism)」の哲学に基づいています。コードの可読性と簡潔さを向上させるために、言語仕様に特別な例外が設けられているのです。
コアとなるコードの変更箇所
このコミットによるコードの変更は、Go言語のドキュメントファイルである doc/effective_go.html
と doc/effective_go.tmpl
への追加のみです。Go言語のコンパイラやランタイムの挙動を変更するものではありません。
具体的には、doc/effective_go.html
と doc/effective_go.tmpl
の両ファイルに、以下のHTMLコンテンツが追加されています。
<h3 id="redeclaration">Redeclaration</h3>
<p>
An aside: The last example in the previous section demonstrates a detail of how the
<code>:=</code> short declaration form works.
The declaration that calls <code>os.Open</code> reads,
</p>
<pre>
f, err := os.Open(name)
</pre>
<p>
This statement declares two variables, <code>f</code> and <code>err</code>.
A few lines later, the call to <code>f.Stat</code> reads,
</p>
<pre>
d, err := f.Stat()
</pre>
<p>
which looks as if it declares <code>d</code> and <code>err</code>.
Notice, though, that <code>err</code> appears in both statements.
This duplication is legal: <code>err</code> is declared by the first statement,
but only <em>re-assigned</em> in the second.
This means that the call to <code>f.Stat</code> uses the existing
<code>err</code> variable declared above, and just gives it a new value.
</p>
<p>
In a <code>:=</code> declaration a variable <code>v</code> may appear even
if it has already been declared, provided:
</p>
<ul>
<li>this declaration is in the same scope as the existing declaration of <code>v</code>
(if <code>v</code> is already declared in an outer scope, the declaration will create a new variable),</li>
<li>the corresponding value in the initialization is assignable to <code>v</code>, and</li>
<li>there is at least one other variable in the declaration that is being declared anew.</li>
</ul>
<p>
This unusual property is pure pragmatism,
making it easy to use a single <code>err</code> value, for example,
in a long <code>if-else</code> chain.
You'll see it used often.
</p>
コアとなるコードの解説
追加されたHTMLコンテンツは、Go言語の:=
ショート変数宣言における「再宣言」のルールを、具体的なコード例を交えて説明しています。
- 導入:
:=
の挙動に関する詳細を説明することを目的としていることを示します。 f, err := os.Open(name)
の例: 最初の:=
宣言でf
とerr
が新しく宣言されることを示します。d, err := f.Stat()
の例: 2番目の:=
宣言でd
が新しく宣言され、err
は既存のものが再代入されることを強調します。- 再宣言の合法性:
err
が両方のステートメントに現れることが合法であり、最初の宣言でerr
が宣言され、2番目の宣言では「再代入」されるだけであることを明確にします。 - 再宣言の3つの条件:
:=
宣言で既存の変数v
が再利用されるための厳密な3つの条件を箇条書きで示します。- 同じスコープ内であること。
- 初期化値が代入可能であること。
- 少なくとも1つの新しい変数が宣言されていること。
- 実用主義: この「珍しい」特性が、
if-else
チェーンでのerr
値の単一利用を容易にするための「純粋な実用主義」に基づいていることを説明し、このパターンが頻繁に使用されることを示唆しています。
このドキュメントの追加により、Go言語の初心者や、この特定の挙動に疑問を持つ開発者に対して、公式かつ明確な説明が提供されることになります。
関連リンク
- Go言語公式ドキュメント: https://go.dev/doc/
- Effective Go: https://go.dev/doc/effective_go (このコミットで変更されたドキュメント)
- Go言語の仕様: https://go.dev/ref/spec
参考にした情報源リンク
- GitHubコミットページ: https://github.com/golang/go/commit/a41006f35a5931387a3111739e4c97ff3568bbcc
- Go issue #2455: https://github.com/golang/go/issues/2455 (Go言語のGitHubリポジトリでこのコミットが修正したとされるissue)
- Go issue #2013: https://github.com/golang/go/issues/2013 (Go言語のGitHubリポジトリでこのコミットが修正したとされるissue)
- Go Code Review Comments: https://go.dev/wiki/CodeReviewComments#declaring-variables (Go言語の変数宣言に関する一般的な慣習)
- Go by Example: Variables: https://gobyexample.com/variables (Go言語の変数に関する基本的な説明)
- Go by Example: Errors: https://gobyexample.com/errors (Go言語のエラーハンドリングに関する基本的な説明)