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

[インデックス 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.Sprintfargsをそのまま渡すと、fmt.Sprintfargs全体を単一の[]interface{}型の引数として解釈してしまいます。その結果、期待される個々の引数としてではなく、スライスそのものがフォーマット文字列に渡され、スライスの文字列表現(例: [v1 v2 v3]のような形式)が出力されてしまうという問題がありました。

このコミットは、この誤った挙動を修正し、fmt.Sprintfargsスライスの各要素を個別の引数として受け取るようにするために、...演算子を追加する必要があるという認識に基づいています。これにより、ドキュメントのコード例が正しく動作し、読者に誤解を与えないようにすることが目的です。

前提知識の解説

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{}を期待します。

  1. 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])のようなエラーメッセージが出力されます)。

  2. 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.Sprintfargsスライス全体を単一の引数として解釈してしまう原因となります。
  • 修正後のコードでは、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言語の初心者にとって特に重要な学習ポイントであり、ドキュメントの正確性を保つ上で不可欠な修正です。

関連リンク

参考にした情報源リンク