
こんにちは。よっしーです(^^)
本日は、Go言語の言語仕様について解説しています。
背景
Go言語を学び始めて、公式の「The Go Programming Language Specification(言語仕様書)」を開いてみたものの、「英語で書かれていて読むのが大変…」「専門用語ばかりで何を言っているのかわからない…」と感じたことはありませんか? 実は、多くのGo初心者が同じ壁にぶつかっています。
言語仕様書は、Go言語の「正式な取扱説明書」のような存在です。プログラミング言語がどのように動くのか、どんなルールで書くべきなのかが詳しく書かれていますが、その分、初めて読む人には難しく感じられるのも事実です。
そこでこの記事では、言語仕様書の導入部分を丁寧な日本語訳とともに、初心者の方でも理解しやすい補足説明を加えてお届けします。「強く型付けされている」「ガベージコレクション」「並行プログラミング」といった専門用語も、具体例を交えながらわかりやすく解説していきます。
言語仕様書は難しそうに見えますが、一つひとつの概念を丁寧に読み解いていけば、必ず理解できます。一緒に、Go言語の基礎をしっかり学んでいきましょう!
値の表現
事前宣言された型の値(インターフェース any および error については後述)、配列、および構造体は自己完結型です。このような値はそれぞれ、すべてのデータの完全なコピーを内包しており、該当する型の変数は値全体を格納します。たとえば、配列変数は配列のすべての要素のための記憶領域(変数)を提供します。それぞれのゼロ値は値の型に固有のものであり、決して nil にはなりません。
nil でないポインタ、関数、スライス、マップ、およびチャネルの値は、複数の値によって共有される可能性のある、基となるデータへの参照を含んでいます。
- ポインタ値は、ポインタの基底型の値を保持している変数への参照です。
- 関数値は、(匿名である可能性のある)関数とクロージャでキャプチャされた変数への参照を含みます。
- スライス値は、スライスの長さ、容量、および基となる配列への参照を含みます。
- マップまたはチャネルの値は、そのマップまたはチャネルの実装固有のデータ構造への参照です。
インターフェース値は、インターフェースの動的型に応じて、自己完結型になる場合もあれば、基となるデータへの参照を含む場合もあります。事前宣言された識別子 nil は、参照を含む可能性のある型のゼロ値です。
複数の値が基となるデータを共有している場合、一方の値を変更すると、もう一方の値も変わる可能性があります。たとえば、スライスの要素を変更すると、その配列を共有するすべてのスライスにおいて、基となる配列の該当要素が変更されます。
解説
たとえ話:「コピー渡し」と「地図の共有」
Go における値の表現方法を理解するために、書類のやり取りで考えてみましょう。
📄 自己完結型(配列・構造体など)
会社の書類をコピー機でコピーして手渡すイメージです。受け取った人がそのコピーに何を書き込んでも、元の書類はまったく影響を受けません。データは完全に独立したコピーとして存在しています。
🗺️ 参照型(スライス・マップ・ポインタなど)
こちらは、会社内で共有しているGoogle マップのリンクを渡すイメージです。リンクを受け取った人が地図上のメモを編集すると、同じリンクを持つ全員が変更後の地図を見ることになります。データは一か所にあり、複数の人が「参照」しているだけです。
コード例
package main
import "fmt"
func main() {
// ==========================================
// 【自己完結型】配列はコピーが渡される
// ==========================================
original := [3]int{1, 2, 3}
copied := original // 配列全体の完全なコピーが作られる
copied[0] = 999 // コピー側を変更しても…
fmt.Println("original:", original) // → [1 2 3](元の配列は変わらない)
fmt.Println("copied: ", copied) // → [999 2 3]
// ==========================================
// 【参照型】スライスは基となる配列を共有する
// ==========================================
base := []int{10, 20, 30}
shared := base // 基となる配列への参照をコピーしているだけ
shared[0] = 999 // shared 側を変更すると…
fmt.Println("base: ", base) // → [999 20 30](base も変わる!)
fmt.Println("shared:", shared) // → [999 20 30]
// ==========================================
// 【参照型】マップも参照を共有する
// ==========================================
m1 := map[string]int{"apple": 100}
m2 := m1 // m2 は m1 と同じマップを指している
m2["apple"] = 999 // m2 を変更すると…
fmt.Println("m1:", m1) // → map[apple:999](m1 も変わる!)
fmt.Println("m2:", m2) // → map[apple:999]
// ==========================================
// 【ゼロ値】参照型のゼロ値は nil
// ==========================================
var s []int // スライスのゼロ値
var m map[string]int // マップのゼロ値
fmt.Println(s == nil) // → true
fmt.Println(m == nil) // → true
var arr [3]int // 配列のゼロ値は nil ではない
fmt.Println(arr) // → [0 0 0](nil にはならない)
}
よくある間違い・注意点
❌ 間違い1:スライスを関数に渡せば「コピー」されると思い込む
func double(s []int) {
s[0] = s[0] * 2 // 呼び出し元のデータも変わってしまう!
}
func main() {
nums := []int{1, 2, 3}
double(nums)
fmt.Println(nums) // → [2 2 3](意図せず変更されている)
}
スライス変数をそのまま渡しても、基となる配列は共有されたままです。元のデータを守りたい場合は
copy()を使って明示的にコピーしましょう。
func doubleSafe(s []int) {
safe := make([]int, len(s))
copy(safe, s) // 基となる配列を別途コピーする
safe[0] = safe[0] * 2
fmt.Println("safe:", safe)
}
❌ 間違い2:nil スライスと空のスライスの混同
var s1 []int // nil スライス(参照なし)
s2 := []int{} // 空スライス(参照あり、要素数ゼロ)
fmt.Println(s1 == nil) // → true
fmt.Println(s2 == nil) // → false(空であっても nil ではない)
fmt.Println(len(s1), len(s2)) // → 0 0(長さは同じ)
JSON エンコードなど、
nilと空スライスを区別するライブラリもあります。意図を明確にするよう意識しましょう。
❌ 間違い3:構造体の中にスライス・マップが含まれる場合
type Team struct {
Members []string
}
t1 := Team{Members: []string{"Alice", "Bob"}}
t2 := t1 // 構造体はコピーされるが…
t2.Members[0] = "Charlie" // スライスの中身は共有されている!
fmt.Println(t1.Members) // → [Charlie Bob](t1 も変わってしまう)
構造体自体はコピーされても、内部の参照型フィールド(スライス・マップ)は基となるデータを共有し続けます。「ディープコピー」が必要な場合は、フィールドを個別にコピーする処理を書きましょう。
まとめ
| 型 | データの扱い | ゼロ値 |
|---|---|---|
| 配列・構造体・基本型 | 値全体をコピー(自己完結) | 型固有の値(nil にならない) |
| スライス | 長さ・容量+基となる配列への参照 | nil |
| マップ | 内部データ構造への参照 | nil |
| ポインタ | 変数のアドレスへの参照 | nil |
| チャネル | 内部データ構造への参照 | nil |
| インターフェース | 動的型による(自己完結または参照) | nil |
Go では「コピーして渡す」か「参照を渡す」かが型によって決まっており、それが予期せぬ副作用を生む原因にもなります。参照型を扱うときは「誰かと同じデータを共有している」という意識を常に持つことが、バグを防ぐ最大のコツです。焦らず一つひとつ確認しながら進めていきましょう! 💪
おわりに
本日は、Go言語の言語仕様について解説しました。

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

コメント