
こんにちは。よっしーです(^^)
本日は、Go言語の言語仕様について解説しています。
背景
Go言語を学び始めて、公式の「The Go Programming Language Specification(言語仕様書)」を開いてみたものの、「英語で書かれていて読むのが大変…」「専門用語ばかりで何を言っているのかわからない…」と感じたことはありませんか? 実は、多くのGo初心者が同じ壁にぶつかっています。
言語仕様書は、Go言語の「正式な取扱説明書」のような存在です。プログラミング言語がどのように動くのか、どんなルールで書くべきなのかが詳しく書かれていますが、その分、初めて読む人には難しく感じられるのも事実です。
そこでこの記事では、言語仕様書の導入部分を丁寧な日本語訳とともに、初心者の方でも理解しやすい補足説明を加えてお届けします。「強く型付けされている」「ガベージコレクション」「並行プログラミング」といった専門用語も、具体例を交えながらわかりやすく解説していきます。
言語仕様書は難しそうに見えますが、一つひとつの概念を丁寧に読み解いていけば、必ず理解できます。一緒に、Go言語の基礎をしっかり学んでいきましょう!
型制約(Type constraints)
型制約とは、対応する型パラメータに許容される型引数の集合を定義し、その型パラメータの値がサポートする操作を制御するインターフェースである [Go 1.18]。
TypeConstraint = TypeElem .
制約が interface{E} という形式のインターフェースリテラルであり、E が埋め込み型要素(メソッドではない)である場合、型パラメータリストにおいては、便宜上、囲んでいる interface{ … } を省略してもよい:
[T []P] // = [T interface{[]P}]
[T ~int] // = [T interface{~int}]
[T int|string] // = [T interface{int|string}]
type Constraint ~int // 不正: ~int は型パラメータリスト内にない
事前宣言されたインターフェース型 comparable は、厳密に比較可能なすべての非インターフェース型の集合を表す [Go 1.18]。
インターフェースは型パラメータでなくても比較可能(comparable)ではあるが、厳密に比較可能(strictly comparable)ではないため、comparable を実装しない。ただし、comparable を満足する(satisfy)ことはできる。
int // comparable を実装する(int は厳密に比較可能)
[]byte // comparable を実装しない(スライスは比較できない)
interface{} // comparable を実装しない(上記参照)
interface{ ~int | ~string } // 型パラメータ専用: comparable を実装する(int, string 型は厳密に比較可能)
interface{ comparable } // 型パラメータ専用: comparable を実装する(comparable は自分自身を実装する)
interface{ ~int | ~[]byte } // 型パラメータ専用: comparable を実装しない(スライスは比較可能ではない)
interface{ ~struct{ any } } // 型パラメータ専用: comparable を実装しない(フィールド any は厳密に比較可能ではない)
comparable インターフェース、および comparable を(直接的または間接的に)埋め込むインターフェースは、型制約としてのみ使用できる。値や変数の型として、あるいは他の非インターフェース型の構成要素として使用することはできない。
翻訳パートはそのままで、解説パートを書き直します。
解説
型制約ってなに?
Go 1.18から「ジェネリクス(総称型)」という機能が使えるようになりました。これは、関数や型を作るとき、「あとから好きな型を当てはめられる」ようにする仕組みです。
ただ、「なんでもOK」だと困ることがあります。たとえば関数の中で + を使いたいのに、渡された型が足し算できない型だったら……? そこで登場するのが型制約です。「この関数には、こういう型だけ渡していいよ」とルールを決めるためのものですね。
// T には int か string だけ渡せるよ、という制約をつけた関数
func PrintValue[T int | string](v T) {
fmt.Println(v)
}
PrintValue(42) // OK! int だから大丈夫
PrintValue("hello") // OK! string だから大丈夫
PrintValue(3.14) // コンパイルエラー! float64 はダメ
型制約の正体は、実はインターフェースです。ただし、普段よく見る「メソッドの集合を定義するインターフェース」とは少し違って、「許可する型の集合」を定義するインターフェースになっています。
interface{...} は省略できる
型パラメータを書くとき、本来は [T interface{~int}] のように interface{...} で囲む必要があります。でも毎回書くのは面倒ですよね。そこで、型パラメータリスト [T ...] の中に限り、この囲みを省略できるようになっています。
// この2つはまったく同じ意味です
func F1[T interface{~int}](v T) {}
func F2[T ~int](v T) {} // こっちのほうがスッキリ!
ただし注意点がひとつ。型パラメータリストの外では省略できません。
type Constraint interface{ ~int } // OK
type Constraint ~int // コンパイルエラー!
~int ってどういう意味?
~(チルダ)は「その型を元にして作られた型も全部OK」という意味です。
Goでは type MyInt int のように、既存の型をもとに新しい型を作れます。~int と書くと、int だけでなく MyInt のような派生型もまとめて許可できます。
type MyInt int // int をもとに作った新しい型
func Double[T ~int](v T) T {
return v * 2
}
Double(MyInt(5)) // OK! MyInt の元は int だからセーフ
Double(5) // OK! int そのものも当然セーフ
もし ~ をつけずに int とだけ書くと、int 型ぴったりしか受け付けません。MyInt は通らなくなります。この違いは地味ですが、ライブラリを作るときにはとても大事です。
comparable ってなに?
comparable は、Goにあらかじめ用意されている特別な型制約で、「== や != で比較できる型」を意味します。
もう少し身近に言うと、mapのキーに使える型と考えるとわかりやすいです。mapのキーには比較できる型しか使えませんよね。それと同じ条件です。
// スライスの中に target があるか調べる関数
func Contains[T comparable](slice []T, target T) bool {
for _, v := range slice {
if v == target { // == が使えることが保証されている!
return true
}
}
return false
}
Contains([]int{1, 2, 3}, 2) // OK!
Contains([]string{"a", "b"}, "a") // OK!
// Contains([][]int{{1}}, []int{1}) // エラー! スライスは == で比較できない
ちょっとややこしい話:「実装する」と「満足する」
ここは正直、Go仕様の中でも難しいところです。ゆっくりいきましょう。
interface{}(= any)について考えてみます。interface{} 型の値は、実行時に == で比較すること自体はできます。でも、中身がスライスだったりするとパニック(実行時エラー)を起こします。つまり、いつでも安全に比較できるとは限らないんですね。
この「いつでも安全に比較できる」ことを、Goの仕様では**厳密に比較可能(strictly comparable)**と呼んでいます。
ここで2つの言葉が出てきます。
| 言葉 | ざっくり言うと |
|---|---|
| implements(実装する) | 型制約のチェックに通る → 型引数として渡せる |
| satisfies(満足する) | 普通のインターフェース代入として互換性がある |
たとえ話で説明しますね。
- 「実装する」 → 運転免許を持っている。確実に安全に運転できる。
- 「満足する」 → 運転席に座ることはできる。でも免許を持っているかはわからない。
interface{} は「運転席に座れる(satisfy)」けど「免許は持っていない(implementしない)」状態です。型制約の場面では免許が求められるので、interface{} を comparable の型引数として渡すことはできません。
comparable は型制約専用
最後にもうひとつ大事なルール。comparable は型制約としてしか使えません。普通の変数の型としては使えないんです。
// OK! 型制約として使う
func F[T comparable](v T) {}
// コンパイルエラー! 変数の型にはできない
var x comparable
なぜかというと、comparable は「こういう性質を持つ型の集合」という概念であって、具体的なひとつの型ではないからです。「果物」というカテゴリーで変数は作れないけど、「りんご」なら作れる、というイメージに近いですね。
おわりに
本日は、Go言語の言語仕様について解説しました。

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

コメント