Go言語入門:言語仕様 -Vol.98-

スポンサーリンク
Go言語入門:言語仕様 -Vol.98- 用語解説
Go言語入門:言語仕様 -Vol.98-
この記事は約9分で読めます。
よっしー
よっしー

こんにちは。よっしーです(^^)

本日は、Go言語の言語仕様について解説しています。

スポンサーリンク

背景

Go言語を学び始めて、公式の「The Go Programming Language Specification(言語仕様書)」を開いてみたものの、「英語で書かれていて読むのが大変…」「専門用語ばかりで何を言っているのかわからない…」と感じたことはありませんか? 実は、多くのGo初心者が同じ壁にぶつかっています。

言語仕様書は、Go言語の「正式な取扱説明書」のような存在です。プログラミング言語がどのように動くのか、どんなルールで書くべきなのかが詳しく書かれていますが、その分、初めて読む人には難しく感じられるのも事実です。

そこでこの記事では、言語仕様書の導入部分を丁寧な日本語訳とともに、初心者の方でも理解しやすい補足説明を加えてお届けします。「強く型付けされている」「ガベージコレクション」「並行プログラミング」といった専門用語も、具体例を交えながらわかりやすく解説していきます。

言語仕様書は難しそうに見えますが、一つひとつの概念を丁寧に読み解いていけば、必ず理解できます。一緒に、Go言語の基礎をしっかり学んでいきましょう!

定数式(Constant expressions)

定数式は定数オペランドのみを含むことができ、コンパイル時に評価される。

型なしの真偽値、数値、および文字列定数は、それぞれ真偽値型、数値型、または文字列型のオペランドを使用することが許される場所であればどこでもオペランドとして使用できる。

定数の比較は常に型なし真偽定数を生成する。定数シフト式の左オペランドが型なし定数である場合、結果は整数定数である。そうでなければ、結果は左オペランドと同じ型の定数であり、その型は整数型でなければならない。

型なし定数に対するその他の演算は、同じ種別の型なし定数を生成する。すなわち、真偽値、整数、浮動小数点、複素数、または文字列定数である。(シフト以外の)二項演算の型なしオペランドが異なる種別である場合、結果は以下のリストでより後に現れるオペランドの種別となる:整数、ルーン、浮動小数点、複素数。たとえば、型なし整数定数を型なし複素数定数で除算すると、型なし複素数定数が生成される。

const a = 2 + 3.0          // a == 5.0   (型なし浮動小数点定数)
const b = 15 / 4           // b == 3     (型なし整数定数)
const c = 15 / 4.0         // c == 3.75  (型なし浮動小数点定数)
const Θ float64 = 3/2      // Θ == 1.0   (float64 型、3/2 は整数除算)
const Π float64 = 3/2.     // Π == 1.5   (float64 型、3/2. は浮動小数点除算)
const d = 1 << 3.0         // d == 8     (型なし整数定数)
const e = 1.0 << 3         // e == 8     (型なし整数定数)
const f = int32(1) << 33   // 不正    (定数 8589934592 は int32 をオーバーフローする)
const g = float64(2) >> 1  // 不正    (float64(2) は型付き浮動小数点定数である)
const h = "foo" > "bar"    // h == true  (型なし真偽定数)
const j = true             // j == true  (型なし真偽定数)
const k = 'w' + 1          // k == 'x'   (型なしルーン定数)
const l = "hi"             // l == "hi"  (型なし文字列定数)
const m = string(k)        // m == "x"   (string 型)
const Σ = 1 - 0.707i       //            (型なし複素数定数)
const Δ = Σ + 2.0e-4       //            (型なし複素数定数)
const Φ = iota*1i - 1/1i   //            (型なし複素数定数)

組み込み関数 complex を型なしの整数、ルーン、または浮動小数点定数に適用すると、型なし複素数定数が生成される。

const ic = complex(0, c)   // ic == 3.75i  (型なし複素数定数)
const iΘ = complex(0, Θ)   // iΘ == 1i     (complex128 型)

定数式は常に正確に評価される。中間値および定数自体は、言語の事前宣言されたいかなる型がサポートするよりも著しく大きい精度を必要とする場合がある。以下は合法な宣言である:

const Huge = 1 << 100         // Huge == 1267650600228229401496703205376  (型なし整数定数)
const Four int8 = Huge >> 98  // Four == 4                                (int8 型)

定数の除算または剰余演算の除数はゼロであってはならない:

3.14 / 0.0   // 不正: ゼロによる除算

型付き定数の値は、常にその定数型の値で正確に表現可能でなければならない。以下の定数式は不正である:

uint(-1)     // -1 は uint として表現できない
int(3.14)    // 3.14 は int として表現できない
int64(Huge)  // 1267650600228229401496703205376 は int64 として表現できない
Four * 300   // オペランド 300 は int8(Four の型)として表現できない
Four * 100   // 積 400 は int8(Four の型)として表現できない

単項ビット反転演算子 ^ で使用されるマスクは非定数の場合のルールと一致する。マスクは符号なし定数の場合はすべて1であり、符号付きおよび型なし定数の場合は -1 である。

^1         // 型なし整数定数、-2 に等しい
uint8(^1)  // 不正: uint8(-2) と同じ、-2 は uint8 として表現できない
^uint8(1)  // 型付き uint8 定数、0xFF ^ uint8(1) = uint8(0xFE) と同じ
int8(^1)   // int8(-2) と同じ
^int8(1)   // -1 ^ int8(1) = -2 と同じ

実装上の制限:コンパイラは型なしの浮動小数点または複素数定数式の計算において丸めを使用することがある。定数の節の実装上の制限を参照のこと。この丸めにより、無限精度で計算すれば整数値になる浮動小数点定数式が整数の文脈で無効になること、およびその逆が起こりうる。


解説

定数式はコンパイル時に計算される

定数式の最大の特徴は、プログラムが実行される前に、コンパイラがすべて計算してしまうことです。

const x = 2 + 3    // コンパイル時に 5 と計算される
const y = x * 10   // コンパイル時に 50 と計算される

実行時にはすでに値が確定しているので、パフォーマンスのコストはゼロです。

型なし定数の「種別」と昇格ルール

型なし定数には「種別(kind)」があります。異なる種別の型なし定数を演算すると、より「広い」種別に自動的に昇格します。

昇格の順序は:整数 → ルーン → 浮動小数点 → 複素数

const a = 2 + 3.0    // 整数 + 浮動小数点 → 浮動小数点(5.0)
const b = 'w' + 1    // ルーン + 整数 → ルーン('x')
const c = 1 - 0.707i // 整数 + 複素数 → 複素数

これは「より表現力の高い種別が勝つ」と覚えるとわかりやすいです。整数は浮動小数点の一部と見なせますし、浮動小数点は複素数の一部と見なせますよね。

整数除算 vs 浮動小数点除算

定数式の中でも特に注意が必要なのが除算です。オペランドの種別によって結果が大きく変わります。

const b = 15 / 4      // 3   (整数 ÷ 整数 → 整数除算、小数切り捨て)
const c = 15 / 4.0    // 3.75(整数 ÷ 浮動小数点 → 浮動小数点除算)

さらに注意が必要なのが、型付き定数への代入です。

const Θ float64 = 3/2   // 1.0(3/2 は整数除算で 1 → float64 に変換して 1.0)
const Π float64 = 3/2.  // 1.5(3/2. は浮動小数点除算で 1.5)

3/2 はまず整数同士の除算として計算されて 1 になり、それから float64 に変換されます。3/2.2. が浮動小数点なので浮動小数点除算になり、1.5 が得られます。この小さなドット1つの違いで結果が変わるのは、よくある落とし穴です。

定数は「無限精度」で計算される

Go の定数式は、通常の型の制限を超えた精度で計算されます。これは驚くべき特徴です。

const Huge = 1 << 100  // 2^100 = 約 1.27 × 10^30
// int64 の最大値は約 9.2 × 10^18 → 全然足りない!
// でも型なし定数としては合法

この巨大な値も、最終的に型に収まりさえすれば問題ありません。

const Four int8 = Huge >> 98  // 2^100 >> 98 = 2^2 = 4 → int8 に収まる!

Hugeint64 にすら入らない巨大な値ですが、シフトして4にすれば int8 に収まります。中間計算が無限精度で行われるからこそ可能な芸当です。

型付き定数の制約

型なし定数は自由に計算できますが、型付き定数は常にその型で表現可能でなければなりません

const Four int8 = 4

Four * 100   // コンパイルエラー! 400 は int8(-128〜127)に収まらない
Four * 300   // コンパイルエラー! 300 自体が int8 に収まらない

2番目の例では、掛け算の結果だけでなく、オペランド 300 自体int8 で表現できないことが問題です。Fourint8 型なので、演算の相手も int8 として扱われるからです。

定数のゼロ除算はコンパイルエラー

実行時のゼロ除算はパニックですが、定数のゼロ除算はコンパイルエラーです。

3.14 / 0.0   // コンパイルエラー!

var x float64 = 0.0
3.14 / x     // 実行時に +Inf(変数なので定数式ではない)

コンパイル時に検出できるものは早い段階でエラーにする、という Go の設計方針ですね。

ビット反転 ^ の挙動

^ の挙動は、定数が型付きかどうか、符号付きか符号なしかで変わります。

^1            // -2(型なし → マスクは -1 → -1 ^ 1 = -2)
^uint8(1)     // 254(uint8 → マスクは 0xFF → 0xFF ^ 1 = 0xFE)
^int8(1)      // -2(int8 → マスクは -1 → -1 ^ 1 = -2)

少しややこしいのが uint8(^1) です。

uint8(^1)     // コンパイルエラー!
// ^1 はまず型なし定数として計算 → -2
// -2 を uint8 に変換しようとする → 表現できない → エラー

^uint8(1) とは順序が違うので注意してください。^uint8(1) は先に uint8(1) を作ってからビット反転するので 0xFE(254)になりますが、uint8(^1) は先に ^1 を計算して -2 にしてから uint8 に変換しようとしてエラーになります。

おわりに 

本日は、Go言語の言語仕様について解説しました。

よっしー
よっしー

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

それでは、また明日お会いしましょう(^^)

コメント

タイトルとURLをコピーしました