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

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

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

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

スポンサーリンク

背景

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

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

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

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

表現可能性

定数 x が型 TT は型パラメータではない)の値として表現可能であるのは、以下の条件のいずれかが満たされる場合です。

  • xT によって定められる値の集合に含まれている。
  • T が浮動小数点型であり、x をオーバーフローなしに T の精度へ丸めることができる。丸めには IEEE 754 の「偶数への丸め(round-to-even)」ルールが使用されますが、IEEE の負のゼロはさらに符号なしのゼロへと単純化されます。なお、定数値は IEEE の負のゼロ・NaN・無限大にはなりません。
  • T が複素数型であり、x の各成分 real(x) および imag(x)T の成分型(float32 または float64)の値として表現可能である。

T が型パラメータである場合、x が型 T の値として表現可能であるのは、xT の型セットに含まれる各型の値として表現可能な場合です。

x                   T           x が T の値として表現可能な理由

'a'                 byte        97 は byte の値の集合に含まれる
97                  rune        rune は int32 のエイリアスであり、97 は 32 ビット整数の集合に含まれる
"foo"               string      "foo" は string の値の集合に含まれる
1024                int16       1024 は 16 ビット整数の集合に含まれる
42.0                byte        42 は符号なし 8 ビット整数の集合に含まれる
1e10                uint64      10000000000 は符号なし 64 ビット整数の集合に含まれる
2.718281828459045   float32     2.718281828459045 は 2.7182817 に丸められ、float32 の値の集合に含まれる
-1e-1000            float64     -1e-1000 は IEEE の -0.0 に丸められ、さらに 0.0 に単純化される
0i                  int         0 は整数値である
(42 + 0i)           float32     42.0(虚部がゼロ)は float32 の値の集合に含まれる
x                   T           x が T の値として表現不可能な理由

0                   bool        0 はブール値の集合に含まれない
'a'                 string      'a' は rune であり、string の値の集合に含まれない
1024                byte        1024 は符号なし 8 ビット整数の集合に含まれない
-1                  uint16      -1 は符号なし 16 ビット整数の集合に含まれない
1.1                 int         1.1 は整数値ではない
42i                 float32     (0 + 42i) は float32 の値の集合に含まれない
1e1000              float64     1e1000 は丸め後に IEEE の +Inf へオーバーフローする

解説

たとえ話:「容器と中身」の相性チェック

表現可能性とは、「この定数の値は、その型という容器に収まるか?」を判断するルールです。


🥛 ぴったり収まる(表現可能)

500ml の牛乳瓶に 350ml の牛乳は問題なく入ります。byte(0〜255)という容器に 42 という値はすっぽり収まります。

🚫 あふれる・形が違う(表現不可能)

500ml の牛乳瓶に 1000ml は入りません(オーバーフロー)。また、牛乳瓶に砂(別の種類)は入れられません(bool 型の容器に 0 という整数は入れられない)。

🔬 浮動小数点は「丸めて収まるか」で判断

定規で測ると 3.14159265… の棒を「小数第2位まで」の容器に入れる場合、3.14 に丸めて収まればOKです。ただし無限大になるほど大きい値は収まりません。


コード例

package main

import "fmt"

func main() {
    // ==========================================
    // 【表現可能】型の値の集合に含まれる場合
    // ==========================================

    // 'a' は byte(= uint8)として表現可能
    // 文字 'a' は内部的に ASCII コード 97 → byte の範囲(0〜255)に収まる
    const charA = 'a'
    var b byte = charA
    fmt.Println(b) // → 97

    // 97 は rune(= int32)として表現可能
    const num97 = 97
    var r rune = num97
    fmt.Println(r) // → 97

    // 42.0 は小数部がゼロなので byte として表現可能
    const fortyTwo = 42.0
    var b2 byte = fortyTwo
    fmt.Println(b2) // → 42

    // 1e10(= 10,000,000,000)は uint64 の範囲に収まる
    const bigNum = 1e10
    var u uint64 = bigNum
    fmt.Println(u) // → 10000000000

    // ==========================================
    // 【表現可能】浮動小数点の丸め
    // ==========================================

    // 2.718281828459045 は float32 の精度に丸めて表現可能
    const e = 2.718281828459045
    var f32 float32 = e
    fmt.Println(f32) // → 2.7182817(float32 精度に丸められる)

    // 非常に小さい負の数は float64 で 0.0 として表現可能
    const tinyNeg = -1e-1000
    var f64 float64 = tinyNeg
    fmt.Println(f64) // → 0(-0.0 → 0.0 に単純化)

    // ==========================================
    // 【表現可能】複素数型の成分ごとに判定
    // ==========================================

    // (42 + 0i):虚部がゼロなので float32 として表現可能
    const complexVal = 42 + 0i
    var f32c float32 = complexVal // 実部 42、虚部 0 → float32 に収まる
    fmt.Println(f32c) // → 42

    // 0i(= 0 + 0i)は int として表現可能
    const zero = 0i
    var i int = zero
    fmt.Println(i) // → 0

    // ==========================================
    // 【表現不可能】コンパイルエラーになる例
    // ==========================================

    // 以下はコメントアウト。実際に有効にするとコンパイルエラーになる

    // var boolVal bool = 0          // 0 は bool の値の集合に含まれない
    // var strVal string = 'a'       // 'a' は rune であり string ではない
    // var byteVal byte = 1024       // 1024 は byte(0〜255)の範囲外
    // var uintVal uint16 = -1       // -1 は符号なし整数に代入不可
    // var intVal int = 1.1          // 1.1 は整数値ではない
    // var floatVal float32 = 42i    // 虚部が非ゼロの複素数は float32 不可
    // var infVal float64 = 1e1000   // 1e1000 はオーバーフローして +Inf になる

    fmt.Println("すべての表現可能な定数の代入に成功しました!")
}

型なし定数がなぜ柔軟なのかを理解しよう

package main

import "fmt"

func main() {
    // 型なし定数は「値そのもの」として存在し、
    // 使われる文脈によって型が決まる
    const x = 42 // 型なし整数定数

    var i int     = x // int として使う
    var f float64 = x // float64 として使う
    var b byte    = x // byte として使う(42 は 0〜255 の範囲内)
    var r rune    = x // rune(int32)として使う

    fmt.Println(i, f, b, r) // → 42 42 42 42

    // 一方、型あり定数は宣言時に型が固定される
    const typedX int = 42
    var i2 int = typedX
    // var f2 float64 = typedX // ← コンパイルエラー!型が int に固定されている
    fmt.Println(i2)
}

型なし定数は「表現可能であれば」さまざまな型に代入できる、非常に柔軟な存在です。Go の定数が使いやすい理由のひとつがこの仕組みです。


よくある間違い・注意点

❌ 間違い1:01 はどんな型にも入ると思い込む

func main() {
    // var boolTrue bool = 1  // ← コンパイルエラー!
    // var boolFalse bool = 0 // ← コンパイルエラー!

    // Go では true/false のみが bool の値の集合
    var t bool = true
    var f bool = false
    fmt.Println(t, f) // → true false
}

C 言語経験者がよく踏む落とし穴です。Go の bool 型は truefalse しか受け付けません。01bool の値の集合に含まれないため表現不可能です。


❌ 間違い2:文字リテラルを string に代入しようとする

func main() {
    // var s string = 'A' // ← コンパイルエラー!
    // 'A' は rune(int32 = 65)であり、string ではない

    // 文字から string を作るには明示的な変換が必要
    var s string = string('A')
    fmt.Println(s) // → A

    // または string リテラル(ダブルクォート)を使う
    var s2 string = "A"
    fmt.Println(s2) // → A
}

シングルクォート 'A'rune(文字コード)、ダブルクォート "A"string です。この区別は Go 初心者が最初に戸惑うポイントのひとつです。


❌ 間違い3:浮動小数点の定数が精度で丸められることを見落とす

import "fmt"

func main() {
    const precise = 2.718281828459045 // 高精度な定数

    var f32 float32 = precise
    var f64 float64 = precise

    // float32 は精度が低いため、丸めが発生する
    fmt.Println(f32) // → 2.7182817(精度が落ちる)
    fmt.Println(f64) // → 2.718281828459045(float64 なら精度が保たれる)

    // 等値比較をすると差が見える
    fmt.Println(float64(f32) == f64) // → false(丸め誤差がある)
}

丸めによって代入自体は成功しますが、精度の損失が起きています。精度が重要な計算では float32 ではなく float64 を使いましょう。


❌ 間違い4:実行時の変数への代入と定数の表現可能性を混同する

func main() {
    // 定数の場合:コンパイル時にチェックされる
    // const tooBig = 1e1000
    // var f float64 = tooBig // ← コンパイルエラー!オーバーフロー

    // 変数の場合:実行時に評価されるため別の話
    x := 1e308
    y := x * 10 // 実行時にオーバーフロー → +Inf になるがエラーにはならない
    fmt.Println(y) // → +Inf(実行時オーバーフロー、パニックにはならない)
}

「表現可能性」はあくまで定数のコンパイル時チェックの話です。変数の実行時演算でのオーバーフローは別のルールが適用されます。


まとめ

定数 xT表現可能か理由
'a'(97)byte97 は 0〜255 の範囲内
42.0byte小数部ゼロ、42 は範囲内
2.718…float32丸めてもオーバーフローしない
-1e-1000float64丸めて 0.0 に単純化される
(42 + 0i)float32虚部ゼロ、実部が範囲内
0boolbool の値の集合は true/false のみ
'a'stringrune は string の値の集合に含まれない
1024byte0〜255 の範囲外
1.1int整数値ではない
1e1000float64丸め後に +Inf へオーバーフロー

「表現可能性」とは、コンパイラが定数の値と型の相性を事前にチェックしてくれる仕組みです。実行してみるまでわからないランタイムエラーとは異なり、コンパイル時に安全性を保証してくれるのが Go の強みのひとつです。型の「容器」と定数の「中身」が合うかどうか、一つひとつ確認していきましょう! 🎯

おわりに 

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

よっしー
よっしー

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

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

コメント

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