
こんにちは。よっしーです(^^)
本日は、Go言語の言語仕様について解説しています。
背景
Go言語を学び始めて、公式の「The Go Programming Language Specification(言語仕様書)」を開いてみたものの、「英語で書かれていて読むのが大変…」「専門用語ばかりで何を言っているのかわからない…」と感じたことはありませんか? 実は、多くのGo初心者が同じ壁にぶつかっています。
言語仕様書は、Go言語の「正式な取扱説明書」のような存在です。プログラミング言語がどのように動くのか、どんなルールで書くべきなのかが詳しく書かれていますが、その分、初めて読む人には難しく感じられるのも事実です。
そこでこの記事では、言語仕様書の導入部分を丁寧な日本語訳とともに、初心者の方でも理解しやすい補足説明を加えてお届けします。「強く型付けされている」「ガベージコレクション」「並行プログラミング」といった専門用語も、具体例を交えながらわかりやすく解説していきます。
言語仕様書は難しそうに見えますが、一つひとつの概念を丁寧に読み解いていけば、必ず理解できます。一緒に、Go言語の基礎をしっかり学んでいきましょう!
... パラメータへの引数の受け渡し(Passing arguments to ... parameters)
f が型 ...T の最後のパラメータ p を持つ可変長引数関数である場合、f 内での p の型は []T 型と等価である。p に対する実引数なしで f が呼び出された場合、p に渡される値は nil である。そうでなければ、渡される値は型 []T の新しいスライスであり、連続する要素が実引数である新しい基底配列を持つ。実引数はすべて T に代入可能でなければならない。したがって、スライスの長さと容量は p に束縛される引数の数であり、呼び出し箇所ごとに異なりうる。
以下の関数と呼び出しが与えられたとき
func Greeting(prefix string, who ...string)
Greeting("nobody")
Greeting("hello:", "Joe", "Anna", "Eileen")
Greeting 内で、who は最初の呼び出しでは nil、2番目の呼び出しでは []string{"Joe", "Anna", "Eileen"} の値を持つ。
最後の引数がスライス型 []T に代入可能であり、その後に ... が続く場合、それは ...T パラメータの値としてそのまま渡される。この場合、新しいスライスは作成されない。
スライス s と呼び出しが与えられたとき
s := []string{"James", "Jasmine"}
Greeting("goodbye:", s...)
Greeting 内で、who は s と同じ値を持ち、同じ基底配列を共有する。
解説
可変長引数(...)ってなに?
可変長引数は、引数の数を固定しない関数を作るための仕組みです。最後のパラメータに ... をつけて宣言します。
func Greeting(prefix string, who ...string) {
for _, name := range who {
fmt.Println(prefix, name)
}
}
Greeting("Hello,", "Alice", "Bob", "Charlie")
// Hello, Alice
// Hello, Bob
// Hello, Charlie
fmt.Println が何個でも引数を受け取れるのも、この仕組みのおかげです。
関数の中ではスライスとして扱われる
...string と書いたパラメータは、関数の中では []string 型のスライスとして使えます。
func Greeting(prefix string, who ...string) {
// who の型は []string
fmt.Println("人数:", len(who))
for _, name := range who {
fmt.Println(prefix, name)
}
}
つまり ... は、呼び出し側では個別の引数として渡せて、受け取る側ではスライスとしてまとめて扱える、という糖衣構文です。
引数がゼロ個のとき
可変長引数に何も渡さないと、パラメータの値は nil になります。
Greeting("nobody")
// who は nil(空のスライスではない!)
nil と空スライス([]string{})は微妙に違いますが、len や range はどちらに対しても正しく動きます。
fmt.Println(len(nil)) // 0
for _, v := range nil { // ループは0回実行される
// ここには来ない
}
なので、通常は nil かどうかを気にする必要はありません。
スライスをそのまま渡す:s... 構文
既にスライスを持っている場合、末尾に ... をつけて渡すと、スライスをそのまま可変長引数に渡せます。
names := []string{"James", "Jasmine"}
Greeting("goodbye:", names...)
ここで重要なのは、... をつけた場合は新しいスライスが作られないことです。関数の中の who は names と同じ基底配列を共有します。
func modify(values ...int) {
values[0] = 999
}
s := []int{1, 2, 3}
modify(s...) // s をそのまま渡す
fmt.Println(s) // [999, 2, 3] ← 元のスライスも変わる!
一方、... をつけずに個別に渡した場合は、新しいスライスが作られるので元のデータに影響しません。
modify(1, 2, 3) // 新しいスライスが作られる → 元データに影響なし
この違いは、前に学んだスライスの基底配列の共有の話とつながっています。
... あり・なしの比較
同じ関数に対する2つの渡し方を比較してみましょう。
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
// 個別に渡す:新しいスライスが作られる
sum(1, 2, 3)
// スライスをそのまま渡す:既存のスライスを共有
s := []int{1, 2, 3}
sum(s...)
どちらも結果は同じ 6 ですが、内部でのスライスの扱いが違います。
よくある使い方
1. append に複数のスライスを結合する
a := []int{1, 2, 3}
b := []int{4, 5, 6}
a = append(a, b...) // b の要素を a に追加
// a = [1, 2, 3, 4, 5, 6]
append の第2引数以降は ...int なので、スライスを渡すときは ... が必要です。
2. ログ関数やラッパー関数
func logInfo(format string, args ...interface{}) {
fmt.Printf("[INFO] "+format+"\n", args...)
// ^^^^^ ここでも ... で転送
}
logInfo("user %s logged in from %s", "Alice", "192.168.1.1")
受け取った可変長引数をそのまま別の可変長引数関数に渡すときにも ... を使います。これは「引数の転送」パターンとして非常によく使われます。
注意点:... は最後のパラメータだけ
可変長引数は、関数の最後のパラメータにだけ使えます。途中に置くことはできません。
func f(a ...int, b string) {} // コンパイルエラー!
func f(a string, b ...int) {} // OK!
また、1つの関数に ... パラメータは1つだけです。
おわりに
本日は、Go言語の言語仕様について解説しました。

何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。
それでは、また明日お会いしましょう(^^)

コメント