
こんにちは。よっしーです(^^)
本日は、Go言語の言語仕様について解説しています。
背景
Go言語を学び始めて、公式の「The Go Programming Language Specification(言語仕様書)」を開いてみたものの、「英語で書かれていて読むのが大変…」「専門用語ばかりで何を言っているのかわからない…」と感じたことはありませんか? 実は、多くのGo初心者が同じ壁にぶつかっています。
言語仕様書は、Go言語の「正式な取扱説明書」のような存在です。プログラミング言語がどのように動くのか、どんなルールで書くべきなのかが詳しく書かれていますが、その分、初めて読む人には難しく感じられるのも事実です。
そこでこの記事では、言語仕様書の導入部分を丁寧な日本語訳とともに、初心者の方でも理解しやすい補足説明を加えてお届けします。「強く型付けされている」「ガベージコレクション」「並行プログラミング」といった専門用語も、具体例を交えながらわかりやすく解説していきます。
言語仕様書は難しそうに見えますが、一つひとつの概念を丁寧に読み解いていけば、必ず理解できます。一緒に、Go言語の基礎をしっかり学んでいきましょう!
オペランド(Operands)
オペランドは、式の中の基本的な値を表す。オペランドは、リテラル、定数・変数・関数を表す(修飾されている可能性のある)ブランクでない識別子、または括弧で囲まれた式のいずれかである。
Operand = Literal | OperandName [ TypeArgs ] | "(" Expression ")" .
Literal = BasicLit | CompositeLit | FunctionLit .
BasicLit = int_lit | float_lit | imaginary_lit | rune_lit | string_lit .
OperandName = identifier | QualifiedIdent .
ジェネリック関数を表すオペランド名の後には、型引数のリストを続けることができる。結果のオペランドはインスタンス化された関数となる。
ブランク識別子は、代入文の左辺においてのみオペランドとして現れることができる。
実装上の制限:空の型集合を持つ型パラメータがオペランドの型である場合、コンパイラはエラーを報告する必要はない。そのような型パラメータを持つ関数はインスタンス化できない。インスタンス化を試みると、インスタンス化の箇所でエラーが発生する。
解説
オペランドってなに?
前回「式は、オペランドに演算子や関数を適用して値を計算するもの」と学びましたね。今回はそのオペランド、つまり「計算の材料」にあたる部分の話です。
たとえば x + 42 という式があったら、x と 42 がオペランドで、+ が演算子です。料理に例えるなら、オペランドは食材、演算子は調理法です。
オペランドの3つの種類
オペランドになれるものは大きく分けて3つあります。
1. リテラル(Literal)
コードに直接書かれた値のことです。
42 // int リテラル
3.14 // float リテラル
1+2i // 虚数リテラル
'A' // rune リテラル(1文字)
"hello" // string リテラル
リテラルにはさらに、構造体やスライスなどをその場で組み立てる複合リテラルと、関数をその場で作る関数リテラルもあります。
[]int{1, 2, 3} // 複合リテラル(スライス)
Point{x: 1, y: 2} // 複合リテラル(構造体)
func(x int) int { return x*2 } // 関数リテラル(無名関数)
2. 識別子(名前)
変数、定数、関数の名前です。
x // 変数名
math.Pi // 修飾された識別子(パッケージ名.識別子名)
fmt.Println // 修飾された識別子
math.Pi のようにパッケージ名がついたものを「修飾された識別子(QualifiedIdent)」と呼びます。自分のパッケージの中にある名前はそのまま、別のパッケージのものは パッケージ名.名前 という形で参照するわけですね。
3. 括弧で囲まれた式
式を () で囲んだものも、それ自体がオペランドになります。
(x + y) * z // (x + y) がひとつのオペランドとして扱われる
算数で (2 + 3) × 4 と書くのと同じ感覚です。括弧の中を先に計算して、その結果がひとつの値(オペランド)になります。
ジェネリック関数のインスタンス化
ジェネリック関数の名前の後ろに型引数をつけると、具体的な型が確定した関数になります。
func min[T ~int|~float64](x, y T) T { ... }
// min[int] は「T が int に確定した min 関数」というオペランド
f := min[int] // f は func(int, int) int 型の値
f(3, 5) // 3 を返す
min だけだと「まだ型が決まっていない設計図」ですが、min[int] とすることで「int 用に完成した関数」になります。これがインスタンス化です。
ブランク識別子 _ の制限
_ はオペランドとして使えますが、代入文の左辺だけです。右辺(値として読み取る側)には使えません。
_, err := os.Open("file.txt") // OK! 左辺で使っている
x := _ // コンパイルエラー! 右辺では使えない
_ は「この値は捨てます」という宣言なので、「捨てたものを読み取る」のは意味がありませんよね。
空の型集合に関する実装上の制限
最後の段落はかなり特殊なケースの話です。型制約が矛盾していて、どの型も当てはまらない場合があります。
// int かつ string を同時に満たす型は存在しない → 型集合が空
type Empty interface {
~int
~string
}
func impossible[T Empty](v T) T {
return v
}
このような関数は定義自体はエラーにならないことがありますが、呼び出そうとした瞬間にエラーになります。当てはめられる型が存在しないので、インスタンス化できないからです。実際に遭遇することはほぼないので、「こういう端っこのケースもある」程度で大丈夫です。
おわりに
本日は、Go言語の言語仕様について解説しました。

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

コメント