[インデックス 12805] ファイルの概要
このコミットは、Go言語の公式ドキュメント「Effective Go」内のコード例における誤りを修正するものです。具体的には、fmt.Sprintf
関数の呼び出しにおいて、可変長引数(variadic arguments)を展開するための...
演算子が欠落していた点を修正し、これにより意図しない出力(スライスがそのまま表示される)が発生する問題を解決しています。
コミット
doc/effective_go.html: Add missing '...' for fmt.Sprintf()
The '...' was missing on a call to fmt.Sprintf() which would result in
the slice being printed instead of the correct result.
R=golang-dev, gri
CC=golang-dev
https://golang.org/cl/5967051
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/cb871ce3e0f573c51be58deaff7971daa6b5a9eb
元コミット内容
commit cb871ce3e0f573c51be58deaff7971daa6b5a9eb
Author: Aaron Kemp <kemp.aaron@gmail.com>
Date: Fri Mar 30 17:51:24 2012 -0700
doc/effective_go.html: Add missing '...' for fmt.Sprintf()
The '...' was missing on a call to fmt.Sprintf() which would result in
the slice being printed instead of the correct result.
R=golang-dev, gri
CC=golang-dev
https://golang.org/cl/5967051
---
doc/effective_go.html | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n
diff --git a/doc/effective_go.html b/doc/effective_go.html
index ed777f4bb8..4382729c3c 100644
--- a/doc/effective_go.html
+++ b/doc/effective_go.html
@@ -2291,7 +2291,7 @@ This would be useful if we wanted to refine the methods of <code>Logger</code>.
</p>
<pre>
func (job *Job) Logf(format string, args ...interface{}) {
- job.Logger.Logf("%q: %s", job.Command, fmt.Sprintf(format, args))\n+ job.Logger.Logf("%q: %s", job.Command, fmt.Sprintf(format, args...))\n }\n </pre>
<p>
変更の背景
この変更の背景には、Go言語の可変長引数(variadic arguments)の扱いに関する重要な仕様があります。fmt.Sprintf
のような関数は、引数の数が不定である可変長引数を受け取ります。この場合、関数定義では...interface{}
のように記述されます。
元のコードでは、fmt.Sprintf(format, args)
と記述されていました。ここでargs
は...interface{}
として定義された可変長引数であり、関数内部では[]interface{}
型のスライスとして扱われます。fmt.Sprintf
にargs
をそのまま渡すと、fmt.Sprintf
はargs
全体を単一の[]interface{}
型の引数として解釈してしまいます。その結果、期待される個々の引数としてではなく、スライスそのものがフォーマット文字列に渡され、スライスの文字列表現(例: [v1 v2 v3]
のような形式)が出力されてしまうという問題がありました。
このコミットは、この誤った挙動を修正し、fmt.Sprintf
がargs
スライスの各要素を個別の引数として受け取るようにするために、...
演算子を追加する必要があるという認識に基づいています。これにより、ドキュメントのコード例が正しく動作し、読者に誤解を与えないようにすることが目的です。
前提知識の解説
1. Go言語の可変長引数 (Variadic Functions)
Go言語では、関数の最後のパラメータに...
を付けることで、任意の数の引数を受け取ることができます。これを可変長引数と呼びます。
例:
func sum(nums ...int) int {
total := 0
for _, num := range nums {
total += num
}
return total
}
このsum
関数は、sum(1, 2, 3)
のように複数のint
型引数を受け取ることができます。関数内部では、nums
は[]int
型のスライスとして扱われます。
2. fmt.Sprintf
関数
fmt.Sprintf
は、Go言語のfmt
パッケージが提供する関数の一つで、C言語のsprintf
に似ています。指定されたフォーマット文字列と引数に基づいて文字列を整形し、その結果の文字列を返します。
例:
name := "Alice"
age := 30
message := fmt.Sprintf("Name: %s, Age: %d", name, age)
// message は "Name: Alice, Age: 30" となる
fmt.Sprintf
は、第一引数にフォーマット文字列、それ以降に可変長引数としてフォーマット対象の値を期待します。
3. ...
演算子の役割(スライス展開)
可変長引数を受け取る関数に、既にスライスとして存在する値を個別の引数として渡したい場合、スライスの後ろに...
演算子を付けます。この演算子は「スライス展開(slice unpacking)」と呼ばれ、スライスの要素を個々の引数として関数に渡す役割を果たします。
例:
nums := []int{1, 2, 3}
total := sum(nums...) // sum(1, 2, 3) と同じ意味になる
もしsum(nums)
と書いた場合、nums
スライス全体が単一の引数として渡され、sum
関数は[]int
型のスライスを引数として受け取るように定義されていないため、コンパイルエラーになります。
4. interface{}
型
Go言語のinterface{}
型は、任意の型の値を保持できる「空のインターフェース」です。これは、異なる型の値をまとめて扱いたい場合や、型が事前にわからない場合に非常に便利です。fmt.Sprintf
の可変長引数が...interface{}
と定義されているのは、あらゆる型の値をフォーマットできるようにするためです。
技術的詳細
このコミットが修正している問題は、Go言語における可変長引数とスライス展開のメカニズムの理解に深く関わっています。
元のコードは以下のようになっていました。
func (job *Job) Logf(format string, args ...interface{}) {
job.Logger.Logf("%q: %s", job.Command, fmt.Sprintf(format, args))
}
ここで、Logf
メソッドのargs
は...interface{}
として定義されており、Logf
の関数本体内では[]interface{}
型のスライスとして扱われます。
問題の箇所はfmt.Sprintf(format, args)
です。
fmt.Sprintf
は、そのシグネチャがfunc Sprintf(format string, a ...interface{}) string
であるため、第二引数以降に可変長引数...interface{}
を期待します。
-
fmt.Sprintf(format, args)
の場合:args
は[]interface{}
型の単一のスライス値です。fmt.Sprintf
にこのスライスをそのまま渡すと、fmt.Sprintf
はこれを「単一のinterface{}
型の引数(その実体は[]interface{}
スライス)」として解釈します。 例えば、args
が[]interface{}{"world", 123}
という内容だったとします。fmt.Sprintf("Hello %s %d", args)
とすると、%s
には[]interface{}{"world", 123}
というスライス全体が渡され、Goのデフォルトのフォーマットルールに従ってスライスの文字列表現(例:[world 123]
)が生成されます。%d
には対応する引数がないため、ゼロ値やエラーが発生する可能性があります(この場合は%d
に対応する引数がないため、fmt
パッケージのルールに従って%d
がそのまま出力されるか、ゼロ値が出力されるか、あるいはパニックになる可能性がありますが、通常は%!(EXTRA string=[world 123])
のようなエラーメッセージが出力されます)。 -
fmt.Sprintf(format, args...)
の場合:args...
と記述することで、args
スライスの要素が個別に展開され、fmt.Sprintf
の可変長引数として渡されます。 例えば、args
が[]interface{}{"world", 123}
だった場合、fmt.Sprintf(format, args...)
は実質的にfmt.Sprintf(format, "world", 123)
として解釈されます。 これにより、fmt.Sprintf
は期待通りに個々の引数を受け取り、フォーマット文字列に従って正しく整形された文字列を生成することができます。
この修正は、Go言語の可変長引数とスライス展開のセマンティクスを正確に理解し、適用することの重要性を示しています。特に、可変長引数を受け取る関数に、既に可変長引数として受け取ったスライスをそのまま渡す際には、必ず...
演算子を使って展開する必要があるという典型的なパターンを修正しています。
コアとなるコードの変更箇所
--- a/doc/effective_go.html
+++ b/doc/effective_go.html
@@ -2291,7 +2291,7 @@ This would be useful if we wanted to refine the methods of <code>Logger</code>.\n </p>\n <pre>\n func (job *Job) Logf(format string, args ...interface{}) {\n- job.Logger.Logf("%q: %s", job.Command, fmt.Sprintf(format, args))\n+ job.Logger.Logf("%q: %s", job.Command, fmt.Sprintf(format, args...))\n }\n </pre>\n <p>\n```
## コアとなるコードの解説
変更されたのは、`doc/effective_go.html`ファイル内のGoコード例の一部です。
元のコード:
```go
func (job *Job) Logf(format string, args ...interface{}) {
job.Logger.Logf("%q: %s", job.Command, fmt.Sprintf(format, args))
}
修正後のコード:
func (job *Job) Logf(format string, args ...interface{}) {
job.Logger.Logf("%q: %s", job.Command, fmt.Sprintf(format, args...))
}
この変更の核心は、fmt.Sprintf(format, args)
がfmt.Sprintf(format, args...)
に変更された点です。
Logf
メソッドはargs ...interface{}
という可変長引数を受け取ります。このargs
は、メソッドの内部では[]interface{}
型のスライスとして扱われます。- 元のコードでは、この
args
スライスがそのままfmt.Sprintf
の引数として渡されていました。前述の「技術的詳細」で説明したように、これはfmt.Sprintf
がargs
スライス全体を単一の引数として解釈してしまう原因となります。 - 修正後のコードでは、
args
の後ろに...
演算子が追加されています。これにより、args
スライスの各要素が個別に展開され、fmt.Sprintf
の可変長引数として渡されます。 - 例えば、
Logf("Hello %s", "World")
が呼び出された場合、args
は[]interface{}{"World"}
となります。- 修正前:
fmt.Sprintf("Hello %s", []interface{}{"World"})
となり、%s
には[World]
という文字列が渡される。 - 修正後:
fmt.Sprintf("Hello %s", "World")
となり、%s
にはWorld
という文字列が渡される。
- 修正前:
この修正により、「Effective Go」ドキュメント内のコード例が正しく動作し、Go言語の可変長引数とスライス展開の正しい使用法を示すようになりました。これは、Go言語の初心者にとって特に重要な学習ポイントであり、ドキュメントの正確性を保つ上で不可欠な修正です。
関連リンク
- Go CL (Change List) 5967051: https://golang.org/cl/5967051
参考にした情報源リンク
- Effective Go - Variadic functions: https://go.dev/doc/effective_go#variadic
- Go by Example: Variadic Functions: https://gobyexample.com/variadic-functions
- fmt package - Sprintf: https://pkg.go.dev/fmt#Sprintf
- The Go Programming Language Specification - Calls: https://go.dev/ref/spec#Calls (特に "Passing arguments to ... parameters" のセクション)