
こんにちは。よっしーです(^^)
本日は、Go言語の言語仕様について解説しています。
背景
Go言語を学び始めて、公式の「The Go Programming Language Specification(言語仕様書)」を開いてみたものの、「英語で書かれていて読むのが大変…」「専門用語ばかりで何を言っているのかわからない…」と感じたことはありませんか? 実は、多くのGo初心者が同じ壁にぶつかっています。
言語仕様書は、Go言語の「正式な取扱説明書」のような存在です。プログラミング言語がどのように動くのか、どんなルールで書くべきなのかが詳しく書かれていますが、その分、初めて読む人には難しく感じられるのも事実です。
そこでこの記事では、言語仕様書の導入部分を丁寧な日本語訳とともに、初心者の方でも理解しやすい補足説明を加えてお届けします。「強く型付けされている」「ガベージコレクション」「並行プログラミング」といった専門用語も、具体例を交えながらわかりやすく解説していきます。
言語仕様書は難しそうに見えますが、一つひとつの概念を丁寧に読み解いていけば、必ず理解できます。一緒に、Go言語の基礎をしっかり学んでいきましょう!
代入可能性
型 V の値 x が型 T の変数に代入可能(「x は T に代入可能」)であるのは、以下の条件のいずれかが満たされる場合です。
VとTが同一である。VとTの基底型が同一であり、かつどちらも型パラメータではなく、VまたはTの少なくとも一方が名前付き型でない。VとTが同一の要素型を持つチャネル型であり、Vが双方向チャネルであり、かつVまたはTの少なくとも一方が名前付き型でない。Tがインターフェース型であり型パラメータではなく、かつxがTを実装している。xが事前宣言された識別子nilであり、Tがポインタ型・関数型・スライス型・マップ型・チャネル型・インターフェース型のいずれかであり、かつ型パラメータでない。xが型なし定数であり、型Tの値として表現可能である。
さらに、x の型 V または T が型パラメータである場合、以下の条件のいずれかが満たされるとき、x は型 T の変数に代入可能です。
xが事前宣言された識別子nilであり、Tが型パラメータであり、かつxがTの型セットに含まれる各型に代入可能である。Vが名前付き型でなく、Tが型パラメータであり、かつxがTの型セットに含まれる各型に代入可能である。Vが型パラメータであり、Tが名前付き型でなく、かつVの型セットに含まれる各型の値がTに代入可能である。
解説
たとえ話:「差し込み口」と「プラグ」の互換性
代入可能性とは、ある値を別の型の変数に「差し込めるか」を判断するルールです。電源プラグとコンセントの互換性で考えてみましょう。
🔌 完全に同じ形(型が同一) 日本の2ピンプラグは日本の2ピンコンセントにそのまま刺さります。型が同一ならば、無条件で代入できます。
🔌 形は違うが変換できる(基底型が同じ、少なくとも一方がリテラル型) 海外の変換アダプターを使えば差し込める場合があります。名前付き型どうしは原則NG ですが、片方がリテラル型(無名の型)であれば、基底型が同じなら変換なしで代入できます。
🔌 インターフェース(汎用コンセント) 「USB-C 対応」と書かれたコンセントには、USB-C の形状さえ満たせばどんなデバイスでも差し込めます。インターフェースを実装していれば、具体的な型が何であれ代入可能です。
代入可能性の条件を整理する
前回学んだ「型の同一性」より緩やかなルールが代入可能性です。図で整理すると以下のようになります。
同一性(Identity)⊂ 代入可能性(Assignability)
型が同一なら必ず代入可能ですが、型が異なっていても代入できるケースがあります。
コード例
package main
import "fmt"
// 型定義の準備
type MyInt int
type MySlice []int
// インターフェース定義
type Stringer interface {
String() string
}
// Stringer を実装する型
type MyName string
func (n MyName) String() string {
return string(n)
}
// 双方向・受信専用チャネルの比較用
type MyChan = chan int // 名前付き型ではない(エイリアス)
func main() {
// ==========================================
// 条件1: V と T が同一
// ==========================================
var a int = 42
var b int = a // int → int:同一型なので当然 OK
fmt.Println(b) // → 42
// ==========================================
// 条件2: 基底型が同じ、少なくとも一方がリテラル型
// ==========================================
var m MyInt = 100
// MyInt(名前付き)→ int(名前付き)は NG
// var n int = m // ← コンパイルエラー!
// リテラル型(無名)を介せば OK
var slice1 MySlice = []int{1, 2, 3}
var slice2 []int = slice1 // MySlice → []int([]int はリテラル型)
fmt.Println(slice2) // → [1 2 3]
// 逆も OK([]int → MySlice も片方がリテラル型)
var slice3 MySlice = []int{4, 5, 6}
fmt.Println(slice3) // → [4 5 6]
_ = m
// ==========================================
// 条件3: チャネル(双方向 → 単方向)
// ==========================================
var bidir chan int = make(chan int, 1) // 双方向チャネル
var recv <-chan int = bidir // 双方向 → 受信専用 OK
var send chan<- int = bidir // 双方向 → 送信専用 OK
bidir <- 99
fmt.Println(<-recv) // → 99
_ = send
// ==========================================
// 条件4: インターフェースを実装している
// ==========================================
var s Stringer
name := MyName("Gopher")
s = name // MyName は Stringer を実装しているので代入 OK
fmt.Println(s.String()) // → Gopher
// ==========================================
// 条件5: nil を参照型に代入
// ==========================================
var ptr *int = nil
var sl []int = nil
var mp map[string]int = nil
var ch chan int = nil
var fn func() = nil
var iface Stringer = nil
fmt.Println(ptr, sl, mp, ch, fn, iface) // → <nil> [] map[] <nil> <nil> <nil>
// ==========================================
// 条件6: 型なし定数
// ==========================================
var f float64 = 3 // 3 は型なし整数定数 → float64 に代入可能
var c complex128 = 0 // 0 は型なし定数 → complex128 に代入可能
const untyped = 255 // 型なし定数
var u8 uint8 = untyped // 255 は uint8 で表現可能
fmt.Println(f, c, u8) // → 3 (0+0i) 255
}
ジェネリクスにおける代入可能性
package main
import "fmt"
// 型セットを持つ制約
type Number interface {
~int | ~float64
}
// 型パラメータを使った関数
func assign[T Number](x T) T {
return x
}
// nil を型パラメータに代入できるか
type Nilable interface {
~*int | ~[]int | ~map[string]int
}
func setNil[T Nilable](x T) T {
// T の型セットに含まれる各型が nil を受け入れるなら代入可能
var zero T // ゼロ値(nil に相当)
return zero
}
func main() {
// V が名前付き型でなく、T が型パラメータ
result := assign(42) // 型なし定数 42 → T(Number)に代入可能
fmt.Println(result) // → 42
// nil を型パラメータに代入
var p *int = nil
fmt.Println(setNil(p)) // → <nil>
}
よくある間違い・注意点
❌ 間違い1:名前付き型どうしは基底型が同じでも代入不可
type Celsius float64
type Fahrenheit float64
func main() {
var c Celsius = 100.0
// var f Fahrenheit = c // ← コンパイルエラー!
// 両方とも名前付き型 → 少なくとも一方がリテラル型、の条件を満たさない
// 解決策:明示的な型変換
var f Fahrenheit = Fahrenheit(c)
fmt.Println(f) // → 100
}
「基底型が同じ」という条件だけでは不十分です。少なくとも一方がリテラル型(名前なし)でなければなりません。名前付き型どうしは明示的な型変換が必要です。
❌ 間違い2:単方向チャネルから双方向チャネルへは代入不可
func main() {
recv := make(<-chan int) // 受信専用チャネル
// var bidir chan int = recv // ← コンパイルエラー!
// 単方向 → 双方向への代入は不可(逆方向のみ許可)
// 双方向 → 単方向なら OK
bidir := make(chan int, 1)
var r <-chan int = bidir // OK
_ = r
}
チャネルの代入は双方向 → 単方向の一方通行です。受信専用・送信専用から双方向チャネルには代入できません。制限を「緩める」方向には戻せないと覚えましょう。
❌ 間違い3:型なし定数でも範囲外の値は代入不可
func main() {
const big = 300
// var u8 uint8 = big // ← コンパイルエラー!
// 300 は uint8(0〜255)の範囲外なので表現不可能
var u16 uint16 = big // uint16(0〜65535)なら OK
fmt.Println(u16) // → 300
}
型なし定数は「その型で表現可能である」という条件が必要です。値が型の範囲に収まっているかをコンパイル時に確認しましょう。
❌ 間違い4:nil はすべての型に代入できると思い込む
func main() {
// var n int = nil // ← コンパイルエラー!int は参照型ではない
// var s string = nil // ← コンパイルエラー!string も参照型ではない
// var a [3]int = nil // ← コンパイルエラー!配列も参照型ではない
// nil を受け入れる型(参照型)
var ptr *int = nil // ポインタ: OK
var sl []int = nil // スライス: OK
var mp map[string]int = nil // マップ: OK
_ = ptr; _ = sl; _ = mp
}
nilを代入できるのはポインタ・関数・スライス・マップ・チャネル・インターフェースに限られます。基本型(int、string、bool)や配列・構造体には代入できません。
まとめ
| 条件 | 例 |
|---|---|
| 型が同一 | int → int |
| 基底型が同じ+少なくとも一方がリテラル型 | MySlice → []int |
| 双方向チャネル → 単方向チャネル | chan int → <-chan int |
| インターフェースを実装 | MyName → Stringer |
| nil → 参照型 | nil → *int、[]int、map など |
| 型なし定数 → 表現可能な型 | 42 → float64、uint8 など |
「型の同一性」がパスポートの完全一致審査だとすれば、「代入可能性」はビザや渡航条件を考慮した入国審査のようなものです。条件を一つひとつ確認する癖をつけることで、コンパイルエラーに慌てることなく対処できるようになります。難しそうに見えますが、実際のコーディングで何度か遭遇するうちに自然と身につきますよ! 💪
おわりに
本日は、Go言語の言語仕様について解説しました。

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

コメント