
こんにちは。よっしーです(^^)
本日は、Go言語の言語仕様について解説しています。
背景
Go言語を学び始めて、公式の「The Go Programming Language Specification(言語仕様書)」を開いてみたものの、「英語で書かれていて読むのが大変…」「専門用語ばかりで何を言っているのかわからない…」と感じたことはありませんか? 実は、多くのGo初心者が同じ壁にぶつかっています。
言語仕様書は、Go言語の「正式な取扱説明書」のような存在です。プログラミング言語がどのように動くのか、どんなルールで書くべきなのかが詳しく書かれていますが、その分、初めて読む人には難しく感じられるのも事実です。
そこでこの記事では、言語仕様書の導入部分を丁寧な日本語訳とともに、初心者の方でも理解しやすい補足説明を加えてお届けします。「強く型付けされている」「ガベージコレクション」「並行プログラミング」といった専門用語も、具体例を交えながらわかりやすく解説していきます。
言語仕様書は難しそうに見えますが、一つひとつの概念を丁寧に読み解いていけば、必ず理解できます。一緒に、Go言語の基礎をしっかり学んでいきましょう!
サイズとアラインメントの保証
数値型について、次のサイズが保証されています。
型 サイズ(バイト)
byte, uint8, int8 1
uint16, int16 2
uint32, int32, float32 4
uint64, int64, float64, complex64 8
complex128 16
次の最小限のアラインメント特性が保証されています。
- 任意の型の変数
xについて、unsafe.Alignof(x)は少なくとも 1 です。 - 構造体型の変数
xについて、unsafe.Alignof(x)は、xの各フィールドfについての値unsafe.Alignof(x.f)のうち最大のものですが、少なくとも 1 です。 - 配列型の変数
xについて、unsafe.Alignof(x)は、その配列の要素型の変数のアラインメントと同じです。
構造体型や配列型は、サイズが 0 より大きいフィールド(または要素)を含まない場合、サイズが 0 になります。2つの異なるサイズ 0 の変数は、メモリ上で同じアドレスを持つことがあります。
解説
結論:数値型が何バイト占めるかはGoが保証している(byteは1、int64やfloat64は8、など)。構造体のアラインメントは「一番大きいフィールドに合わせる」。中身が空の構造体・配列はサイズ0で、別々の変数が同じアドレスを共有することすらある。
前の節の unsafe の続きで、引き続き低レベルな内容です。ただし前半のサイズ表は比較的わかりやすく、知っておくと役立つ場面もあります。
数値型のサイズは保証されている
Goは、各数値型がメモリ上で正確に何バイトを占めるかを言語仕様として保証しています。これは環境(OSやCPU)が変わっても変わりません。
| 型 | サイズ |
|---|---|
byte, uint8, int8 | 1バイト |
uint16, int16 | 2バイト |
uint32, int32, float32 | 4バイト |
uint64, int64, float64, complex64 | 8バイト |
complex128 | 16バイト |
型名の末尾の数字がビット数を表しているのがポイントです。1バイト = 8ビットなので、変換すると一致します。たとえば int32 は32ビット = 4バイト、int64 は64ビット = 8バイトです。名前を見ればサイズが分かる、という親切な命名になっています。
unsafe.Sizeof(int32(0)) // 4
unsafe.Sizeof(int64(0)) // 8
unsafe.Sizeof(float64(0)) // 8
なお byte は uint8 の別名(エイリアス)で、どちらも1バイトです。complex64(複素数)は、内部的に float32 を2つ(実部と虚部)持つので 4×2 = 8バイト、complex128 は float64 を2つで 8×2 = 16バイトになります。
注意点として、ここに int や uint(数字の付かないもの)が含まれていないことに気づいてください。これらはサイズが固定されておらず、環境によって32ビットだったり64ビットだったりします(現代の多くの環境では64ビット=8バイト)。サイズを固定したい場合は int64 のように明示的な型を使う、というのが実務上の指針です。
アラインメントの3つの保証
前の節で触れた「アラインメント(メモリ上の配置の境界)」について、最低限の保証が3つ示されています。
保証1:どんな型でもアラインメントは最低1
unsafe.Alignof(x) >= 1 // どんな x でも必ず成り立つ
アラインメントが0や負になることはなく、最低でも1です。1は「どんなアドレスでもよい(制約なし)」を意味します。
保証2:構造体のアラインメントは「最大のフィールドに合わせる」
構造体のアラインメントは、その中のフィールドたちのアラインメントのうち一番大きいものになります。
type S struct {
a int8 // アラインメント 1
b int64 // アラインメント 8
c int16 // アラインメント 2
}
// S 全体のアラインメントは、最大の 8(int64 に合わせる)
引っ越しのトラックに例えると、一番大きい家具(int64)が入る区画の基準に合わせて、荷台全体の積み込みルールが決まる、というイメージです。一番厳しい要求に全体を合わせる、ということです。
保証3:配列のアラインメントは「要素1個と同じ」
var arr [10]int32
// arr のアラインメントは、int32 単体のアラインメント(=4)と同じ
配列は同じ型がただ並んだものなので、要素1個分のアラインメントがそのまま配列全体のアラインメントになります。要素が何個並んでも変わりません。
サイズ0の型という不思議な存在
最後の段落は、初心者には驚きの内容かもしれません。中身が空の構造体や配列は、サイズが0になります。
type Empty struct{} // フィールドが1つもない → サイズ 0
unsafe.Sizeof(Empty{}) // 0
var arr [0]int // 要素0個の配列 → サイズ 0
「サイズが大きいフィールド(要素)を1つも含まない」ものはサイズ0になる、というのが原文の規定です。代表は空構造体 struct{} です。これは何のデータも持たない型で、メモリを一切消費しません。
そして最も奇妙なのが最後の一文です。サイズ0の変数2つが、メモリ上で同じアドレスを持つことがある、という規定です。
var a, b struct{}
// &a と &b が同じアドレスを指すことがあり得る
普通の変数なら、別々の変数は別々のアドレスを持つはずです。しかしサイズ0の変数は「場所を取らない」ので、Goは複数の変数を同じ場所に重ねて配置してよい、ということになっています。広さ0の土地が無数にあっても、すべて同じ一点に存在できる、というような数学的な不思議さです。
空構造体は実際に役立つ
「データを持たない型に何の意味があるのか」と思うかもしれませんが、空構造体 struct{} は実務でちゃんと使われます。「値そのものに意味はなく、存在の有無や通知だけが欲しい」場面で、メモリを無駄にしない型として活躍します。
代表例が2つあります。
集合(Set)としてのマップ:
seen := map[string]struct{}{} // 値は不要、キーの有無だけ使いたい
seen["apple"] = struct{}{} // 「apple を見た」という事実だけ記録
_, ok := seen["apple"] // ok が true なら存在する
Goには専用の集合型がないので、マップの値に struct{} を使って「キーの集合」を表現します。値にメモリを使わないので効率的です。
通知だけのチャネル:
done := make(chan struct{}) // 完了の合図を送るだけのチャネル
done <- struct{}{} // 「終わった」という合図(中身に意味はない)
「データを運ぶ」のではなく「合図を送る」ためだけのチャネルに使われます。
補足:この節は前節の unsafe と同じく低レベル寄りですが、2つだけ実用的に覚えておく価値があります。第一に、サイズを厳密に固定したいときは int ではなく int32/int64 のようなビット数付きの型を使うこと。第二に、空構造体 struct{} は「メモリを使わない目印」として、集合や通知用チャネルで実際によく使われること。この2点は unsafe を直接使わない場面でも役立つ知識です。
おわりに
本日は、Go言語の言語仕様について解説しました。

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

コメント