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

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

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

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

スポンサーリンク

背景

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

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

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

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

Rune literals(ルーンリテラル)

ルーンリテラルは、Unicodeコードポイントを識別する整数値であるルーン定数を表します。ルーンリテラルは、'x''\n'のように、シングルクォートで囲まれた1つ以上の文字として表現されます。クォート内には、改行とエスケープされていないシングルクォートを除き、任意の文字を使用できます。シングルクォートで囲まれた単一の文字は、その文字自体のUnicode値を表し、バックスラッシュで始まる複数文字のシーケンスは、さまざまな形式で値をエンコードします。

最も単純な形式は、クォート内の単一の文字を表します。Goのソーステキストは、UTF-8でエンコードされたUnicode文字であるため、複数のUTF-8エンコードされたバイトが単一の整数値を表す場合があります。たとえば、リテラル'a'は、リテラルa、Unicode U+0061、値0x61を表す単一のバイトを保持し、'ä'は、リテラルa-ウムラウト、U+00E4、値0xe4を表す2バイト(0xc3 0xa4)を保持します。

いくつかのバックスラッシュエスケープにより、任意の値をASCIIテキストとしてエンコードできます。整数値を数値定数として表現する方法は4つあります。\xの後に正確に2桁の16進数、\uの後に正確に4桁の16進数、\Uの後に正確に8桁の16進数、そして単純なバックスラッシュ\の後に正確に3桁の8進数です。いずれの場合も、リテラルの値は、対応する基数の数字で表される値です。

これらの表現はすべて整数になりますが、有効な範囲が異なります。8進エスケープは、0から255までの値を表す必要があります。16進エスケープは、構造上この条件を満たします。エスケープ\u\UはUnicodeコードポイントを表すため、その中のいくつかの値は不正です。特に、0x10FFFFを超える値やサロゲートハーフは不正です。

バックスラッシュの後の特定の単一文字エスケープは、特殊な値を表します。

\a   U+0007 アラートまたはベル
\b   U+0008 バックスペース
\f   U+000C フォームフィード
\n   U+000A ラインフィードまたは改行
\r   U+000D キャリッジリターン
\t   U+0009 水平タブ
\v   U+000B 垂直タブ
\\   U+005C バックスラッシュ
\'   U+0027 シングルクォート(ルーンリテラル内でのみ有効)
\"   U+0022 ダブルクォート(文字列リテラル内でのみ有効)

ルーンリテラル内でバックスラッシュの後に認識されない文字が続く場合は不正です。

rune_lit         = "'" ( unicode_value | byte_value ) "'" .
unicode_value    = unicode_char | little_u_value | big_u_value | escaped_char .
byte_value       = octal_byte_value | hex_byte_value .
octal_byte_value = `\` octal_digit octal_digit octal_digit .
hex_byte_value   = `\` "x" hex_digit hex_digit .
little_u_value   = `\` "u" hex_digit hex_digit hex_digit hex_digit .
big_u_value      = `\` "U" hex_digit hex_digit hex_digit hex_digit
                           hex_digit hex_digit hex_digit hex_digit .
escaped_char     = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | "'" | `"` ) .
'a'
'ä'
'本'
'\t'
'\000'
'\007'
'\377'
'\x07'
'\xff'
'\u12e4'
'\U00101234'
'\''         // シングルクォート文字を含むルーンリテラル
'aa'         // 不正: 文字が多すぎる
'\k'         // 不正: k はバックスラッシュの後で認識されない
'\xa'        // 不正: 16進数の桁が少なすぎる
'\0'         // 不正: 8進数の桁が少なすぎる
'\400'       // 不正: 8進数の値が255を超えている
'\uDFFF'     // 不正: サロゲートハーフ
'\U00110000' // 不正: 無効なUnicodeコードポイント

解説

ルーンとは何か?

ルーン(rune) は、Goで1つのUnicode文字を表すデータ型です。内部的にはint32型で、Unicodeコードポイント(文字に割り当てられた番号)を保存します。

たとえ話: 世界中のすべての文字(アルファベット、ひらがな、漢字、絵文字など)に番号が割り当てられていて、その番号を保存するのがルーンです。

package main

import "fmt"

func main() {
    // ルーンリテラルの基本
    var r1 rune = 'A'    // 英字
    var r2 rune = 'あ'   // ひらがな
    var r3 rune = '漢'   // 漢字
    var r4 rune = '😀'   // 絵文字
    
    fmt.Println(r1, r2, r3, r4)  // 65 12354 28450 128512 (Unicode値)
    fmt.Printf("%c %c %c %c\n", r1, r2, r3, r4)  // A あ 漢 😀
}

1. ルーンリテラルの基本形

シングルクォートで囲む

ルーンリテラルはシングルクォート(') で囲みます。ダブルクォート(")は文字列用です。

package main

import "fmt"

func main() {
    // ✅ 正しい(ルーン)
    var char rune = 'A'
    
    // ❌ 間違い(これは文字列)
    // var char2 rune = "A"  // コンパイルエラー
    
    fmt.Printf("文字: %c, Unicode値: %d\n", char, char)
}

1文字だけを囲む

package main

import "fmt"

func main() {
    // ✅ 正しい例
    var a rune = 'a'
    var b rune = 'あ'
    var c rune = '漢'
    var d rune = '😀'
    
    // ❌ 間違った例(複数文字は不可)
    // var e rune = 'aa'  // コンパイルエラー: too many characters
    
    fmt.Printf("%c %c %c %c\n", a, b, c, d)
}

2. 様々な文字の表現

ASCII文字

package main

import "fmt"

func main() {
    // アルファベット
    var upper rune = 'A'  // U+0041 (65)
    var lower rune = 'a'  // U+0061 (97)
    
    // 数字
    var digit rune = '5'  // U+0035 (53) ← 数値の5ではなく文字の'5'
    
    // 記号
    var symbol rune = '!'  // U+0021 (33)
    
    fmt.Printf("%c=%d, %c=%d, %c=%d, %c=%d\n",
        upper, upper, lower, lower, digit, digit, symbol, symbol)
}

日本語文字

package main

import "fmt"

func main() {
    // ひらがな
    var hiragana rune = 'あ'  // U+3042
    
    // カタカナ
    var katakana rune = 'ア'  // U+30A2
    
    // 漢字
    var kanji rune = '本'     // U+672C
    
    fmt.Printf("ひらがな: %c (U+%04X)\n", hiragana, hiragana)
    fmt.Printf("カタカナ: %c (U+%04X)\n", katakana, katakana)
    fmt.Printf("漢字: %c (U+%04X)\n", kanji, kanji)
}

多言語・絵文字

package main

import "fmt"

func main() {
    // ギリシャ文字
    var alpha rune = 'α'     // U+03B1
    
    // 中国語
    var chinese rune = '中'   // U+4E2D
    
    // アラビア語
    var arabic rune = 'ع'     // U+0639
    
    // 絵文字
    var emoji1 rune = '😀'    // U+1F600
    var emoji2 rune = '🎉'    // U+1F389
    
    fmt.Printf("%c %c %c %c %c\n", alpha, chinese, arabic, emoji1, emoji2)
}

3. エスケープシーケンス

特殊文字のエスケープ

バックスラッシュ\を使って、特殊な文字を表現できます。

package main

import "fmt"

func main() {
    // 改行
    var newline rune = '\n'  // U+000A
    
    // タブ
    var tab rune = '\t'      // U+0009
    
    // バックスラッシュ
    var backslash rune = '\\' // U+005C
    
    // シングルクォート
    var quote rune = '\''    // U+0027
    
    fmt.Printf("改行コード: %d\n", newline)
    fmt.Printf("タブコード: %d\n", tab)
    fmt.Printf("バックスラッシュ: %c\n", backslash)
    fmt.Printf("クォート: %c\n", quote)
}

すべてのエスケープシーケンス

エスケープUnicode10進数説明
\aU+00077アラート(ベル音)
\bU+00088バックスペース
\fU+000C12フォームフィード(改ページ)
\nU+000A10改行(ラインフィード)
\rU+000D13キャリッジリターン
\tU+00099水平タブ
\vU+000B11垂直タブ
\\U+005C92バックスラッシュ
\'U+002739シングルクォート
\"U+002234ダブルクォート(文字列用)
package main

import "fmt"

func main() {
    fmt.Println("Hello\nWorld")     // 改行
    fmt.Println("Col1\tCol2\tCol3") // タブ区切り
    fmt.Println("Path: C:\\Users")  // バックスラッシュ
    fmt.Printf("Quote: %c\n", '\'') // シングルクォート
}

4. 数値によるエスケープ

8進数エスケープ:

8進数エスケープ: \000

0

正確に3桁の8進数で文字を指定します。範囲は\000\377(0〜255)。

package main

import "fmt"

func main() {
    // 8進数でASCII文字を表現
    var a rune = '\101'  // 8進数の101 = 10進数の65 = 'A'
    var newline rune = '\012'  // 8進数の012 = 10進数の10 = '\n'
    var del rune = '\177'      // 8進数の177 = 10進数の127 = DEL
    
    fmt.Printf("%c (U+%04X)\n", a, a)
    fmt.Printf("改行コード: %d\n", newline)
    fmt.Printf("DEL: %d\n", del)
    
    // ✅ 正しい例
    var valid1 rune = '\000'  // 0
    var valid2 rune = '\377'  // 255
    
    // ❌ 間違った例
    // var invalid1 rune = '\0'    // エラー: 桁が足りない
    // var invalid2 rune = '\400'  // エラー: 255を超えている
    
    fmt.Println(valid1, valid2)
}

16進数エスケープ: \xHH

\xの後に正確に2桁の16進数で文字を指定します。範囲は\x00\xFF(0〜255)。

package main

import "fmt"

func main() {
    // 16進数でASCII文字を表現
    var a rune = '\x41'  // 16進数の41 = 10進数の65 = 'A'
    var newline rune = '\x0A'  // 16進数の0A = 10進数の10 = '\n'
    var space rune = '\x20'    // 16進数の20 = 10進数の32 = ' '
    
    fmt.Printf("%c (U+%04X)\n", a, a)
    fmt.Printf("改行コード: %d\n", newline)
    fmt.Printf("スペース: %c\n", space)
    
    // ✅ 正しい例
    var valid rune = '\xFF'  // 255
    
    // ❌ 間違った例
    // var invalid rune = '\xa'  // エラー: 桁が足りない(2桁必要)
    
    fmt.Println(valid)
}

Unicode小文字エスケープ: \uHHHH

\uの後に正確に4桁の16進数でUnicodeコードポイントを指定します。

package main

import "fmt"

func main() {
    // 4桁のUnicodeエスケープ
    var hiragana rune = '\u3042'  // ひらがなの「あ」
    var kanji rune = '\u672C'     // 漢字の「本」
    var symbol rune = '\u2603'    // ☃ (雪だるま)
    
    fmt.Printf("%c (U+%04X)\n", hiragana, hiragana)
    fmt.Printf("%c (U+%04X)\n", kanji, kanji)
    fmt.Printf("%c (U+%04X)\n", symbol, symbol)
    
    // ✅ 正しい例
    var valid rune = '\u12e4'
    
    // ❌ 間違った例
    // var invalid1 rune = '\u12e'   // エラー: 桁が足りない
    // var invalid2 rune = '\uDFFF'  // エラー: サロゲートハーフ(不正な範囲)
    
    fmt.Println(valid)
}

Unicode大文字エスケープ: \UHHHHHHHH

\Uの後に正確に8桁の16進数でUnicodeコードポイントを指定します。絵文字など、U+10000以上の文字に使います。

package main

import "fmt"

func main() {
    // 8桁のUnicodeエスケープ
    var emoji1 rune = '\U0001F600'  // 😀
    var emoji2 rune = '\U0001F389'  // 🎉
    var emoji3 rune = '\U0001F4BB'  // 💻
    
    fmt.Printf("%c (U+%08X)\n", emoji1, emoji1)
    fmt.Printf("%c (U+%08X)\n", emoji2, emoji2)
    fmt.Printf("%c (U+%08X)\n", emoji3, emoji3)
    
    // ✅ 正しい例
    var valid rune = '\U00101234'
    
    // ❌ 間違った例
    // var invalid rune = '\U00110000'  // エラー: U+10FFFFを超えている
    
    fmt.Println(valid)
}

5. UTF-8とバイト表現

1バイト文字(ASCII)

package main

import "fmt"

func main() {
    // ASCIIは1バイトで表現される
    var a rune = 'a'
    
    fmt.Printf("文字: %c\n", a)
    fmt.Printf("Unicode: U+%04X\n", a)
    fmt.Printf("10進数: %d\n", a)
    fmt.Printf("16進数: 0x%02X\n", a)
    
    // 出力:
    // 文字: a
    // Unicode: U+0061
    // 10進数: 97
    // 16進数: 0x61
}

複数バイト文字

package main

import (
    "fmt"
    "unicode/utf8"
)

func main() {
    // ひらがな「あ」は3バイトで表現される
    var hiragana rune = 'あ'
    
    // UTF-8エンコード
    buf := make([]byte, 4)
    n := utf8.EncodeRune(buf, hiragana)
    
    fmt.Printf("文字: %c\n", hiragana)
    fmt.Printf("Unicode: U+%04X\n", hiragana)
    fmt.Printf("バイト数: %d\n", n)
    fmt.Printf("バイト列: % X\n", buf[:n])  // E3 81 82
    
    // 絵文字「😀」は4バイトで表現される
    var emoji rune = '😀'
    n2 := utf8.EncodeRune(buf, emoji)
    
    fmt.Printf("\n文字: %c\n", emoji)
    fmt.Printf("Unicode: U+%08X\n", emoji)
    fmt.Printf("バイト数: %d\n", n2)
    fmt.Printf("バイト列: % X\n", buf[:n2])  // F0 9F 98 80
}

6. 実用例

例1: 文字の種類判定

package main

import (
    "fmt"
    "unicode"
)

func main() {
    chars := []rune{'A', 'a', '5', '!', 'あ', '漢', '😀'}
    
    for _, r := range chars {
        fmt.Printf("'%c': ", r)
        
        if unicode.IsLetter(r) {
            fmt.Print("文字 ")
        }
        if unicode.IsDigit(r) {
            fmt.Print("数字 ")
        }
        if unicode.IsUpper(r) {
            fmt.Print("大文字 ")
        }
        if unicode.IsLower(r) {
            fmt.Print("小文字 ")
        }
        if unicode.IsPunct(r) {
            fmt.Print("句読点 ")
        }
        if unicode.IsSpace(r) {
            fmt.Print("空白 ")
        }
        
        fmt.Println()
    }
}

例2: 大文字・小文字変換

package main

import (
    "fmt"
    "unicode"
)

func main() {
    // ASCII文字
    var lower rune = 'a'
    var upper rune = unicode.ToUpper(lower)
    fmt.Printf("%c → %c\n", lower, upper)  // a → A
    
    // 日本語には大文字小文字の概念がない
    var hiragana rune = 'あ'
    fmt.Printf("%c → %c\n", hiragana, unicode.ToUpper(hiragana))  // あ → あ
    
    // ギリシャ文字
    var alpha rune = 'α'
    fmt.Printf("%c → %c\n", alpha, unicode.ToUpper(alpha))  // α → Α
}

例3: ルーンと文字列の相互変換

package main

import "fmt"

func main() {
    // ルーン → 文字列
    var r rune = 'あ'
    str := string(r)
    fmt.Printf("ルーン %c → 文字列 %s\n", r, str)
    
    // 文字列 → ルーンスライス
    text := "こんにちは"
    runes := []rune(text)
    fmt.Printf("文字列 %s には %d 文字\n", text, len(runes))
    
    for i, r := range runes {
        fmt.Printf("%d: %c (U+%04X)\n", i, r, r)
    }
}

例4: CSV出力でのタブとカンマ

package main

import "fmt"

func main() {
    // タブ区切り
    fmt.Printf("名前\t年齢\t都市\n")
    fmt.Printf("太郎\t25\t東京\n")
    fmt.Printf("花子\t30\t大阪\n")
    
    // カンマ区切り
    fmt.Println("\nCSV形式:")
    fmt.Println("名前,年齢,都市")
    fmt.Println("太郎,25,東京")
    fmt.Println("花子,30,大阪")
}

7. よくある間違い

間違い1: 複数文字を入れる

// ❌ 間違い
// var r rune = 'abc'  // エラー: too many characters

// ✅ 正しい
var r1 rune = 'a'
var r2 rune = 'b'
var r3 rune = 'c'

間違い2: ダブルクォートを使う

// ❌ 間違い(これは文字列)
// var r rune = "a"  // エラー: cannot use "a" (type string) as type rune

// ✅ 正しい
var r rune = 'a'

間違い3: エスケープの桁数が間違っている

// ❌ 間違い
// var r1 rune = '\0'     // エラー: 8進数は3桁必要
// var r2 rune = '\xa'    // エラー: 16進数は2桁必要
// var r3 rune = '\u12e'  // エラー: \uは4桁必要

// ✅ 正しい
var r1 rune = '\000'
var r2 rune = '\x0a'
var r3 rune = '\u012e'

間違い4: 無効な範囲

// ❌ 間違い
// var r1 rune = '\400'       // エラー: 8進数は255まで
// var r2 rune = '\uDFFF'     // エラー: サロゲートハーフ
// var r3 rune = '\U00110000' // エラー: U+10FFFFまで

// ✅ 正しい
var r1 rune = '\377'       // 255
var r2 rune = '\uD7FF'     // サロゲート範囲の直前
var r3 rune = '\U0010FFFF' // 最大値

まとめ: ルーンリテラルで覚えておくべきこと

ルーンリテラルの書き方

  1. シングルクォートで囲む: 'a', 'あ', '😀'
  2. 1文字だけ: 複数文字は不可
  3. エスケープシーケンス: '\n', '\t', '\\'

エスケープ方法の一覧

方法形式桁数範囲
8進数\0003桁0〜255'\101' (A)
16進数\xFF2桁0〜255'\x41' (A)
Unicode小\u00004桁0〜U+FFFF'\u3042' (あ)
Unicode大\U000000008桁0〜U+10FFFF'\U0001F600' (😀)

実用的なアドバイス

package main

import (
    "fmt"
    "unicode"
)

func main() {
    // 基本的な使い方
    var char rune = 'A'
    fmt.Printf("文字: %c, Unicode: U+%04X, 値: %d\n", char, char, char)
    
    // エスケープシーケンス
    fmt.Println("1行目\n2行目")
    fmt.Println("列1\t列2\t列3")
    
    // Unicode判定
    if unicode.IsLetter('あ') {
        fmt.Println("「あ」は文字です")
    }
    
    // 文字列との変換
    str := string(char)
    runes := []rune("こんにちは")
    
    fmt.Println(str, runes)
}

ルーンは、国際化対応のプログラムを書く上で重要な概念です。世界中の文字を統一的に扱えるGoの強力な機能です!

おわりに 

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

よっしー
よっしー

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

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

コメント

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