
こんにちは。よっしーです(^^)
本日は、Go言語の言語仕様について解説しています。
背景
Go言語を学び始めて、公式の「The Go Programming Language Specification(言語仕様書)」を開いてみたものの、「英語で書かれていて読むのが大変…」「専門用語ばかりで何を言っているのかわからない…」と感じたことはありませんか? 実は、多くのGo初心者が同じ壁にぶつかっています。
言語仕様書は、Go言語の「正式な取扱説明書」のような存在です。プログラミング言語がどのように動くのか、どんなルールで書くべきなのかが詳しく書かれていますが、その分、初めて読む人には難しく感じられるのも事実です。
そこでこの記事では、言語仕様書の導入部分を丁寧な日本語訳とともに、初心者の方でも理解しやすい補足説明を加えてお届けします。「強く型付けされている」「ガベージコレクション」「並行プログラミング」といった専門用語も、具体例を交えながらわかりやすく解説していきます。
言語仕様書は難しそうに見えますが、一つひとつの概念を丁寧に読み解いていけば、必ず理解できます。一緒に、Go言語の基礎をしっかり学んでいきましょう!
String literals(文字列リテラル)
文字列リテラルは、文字のシーケンスを連結して得られる文字列定数を表します。文字列リテラルには、生文字列リテラルと解釈される文字列リテラルの2つの形式があります。
生文字列リテラルは、`foo`のように、バッククォート間の文字シーケンスです。クォート内には、バッククォートを除く任意の文字を使用できます。生文字列リテラルの値は、クォート間の解釈されない(暗黙的にUTF-8エンコードされた)文字で構成される文字列です。特に、バックスラッシュは特別な意味を持たず、文字列に改行を含めることができます。生文字列リテラル内のキャリッジリターン文字(‘\r’)は、生文字列値から破棄されます。
解釈される文字列リテラルは、"bar"のように、ダブルクォート間の文字シーケンスです。クォート内には、改行とエスケープされていないダブルクォートを除く任意の文字を使用できます。クォート間のテキストがリテラルの値を形成し、バックスラッシュエスケープはルーンリテラルと同様に解釈されます(\'は不正で\"は正当)。同じ制限が適用されます。3桁の8進数(\nnn)と2桁の16進数(\xnn)のエスケープは、結果の文字列の個々のバイトを表します。他のすべてのエスケープは、個々の文字の(場合によっては複数バイトの)UTF-8エンコーディングを表します。したがって、文字列リテラル内の\377と\xFFは値0xFF=255の単一のバイトを表し、ÿ, \u00FF, \U000000FF, \xc3\xbfは文字U+00FFのUTF-8エンコーディングの2バイト0xc3 0xbfを表します。
string_lit = raw_string_lit | interpreted_string_lit .
raw_string_lit = "`" { unicode_char | newline } "`" .
interpreted_string_lit = `"` { unicode_value | byte_value } `"` .
`abc` // "abc"と同じ
`\n
\n` // "\\n\n\\n"と同じ
"\n"
"\"" // `"`と同じ
"Hello, world!\n"
"日本語"
"\u65e5本\U00008a9e"
"\xff\u00FF"
"\uD800" // 不正: サロゲートハーフ
"\U00110000" // 不正: 無効なUnicodeコードポイント
これらの例はすべて同じ文字列を表します:
"日本語" // UTF-8入力テキスト
`日本語` // 生リテラルとしてのUTF-8入力テキスト
"\u65e5\u672c\u8a9e" // 明示的なUnicodeコードポイント
"\U000065e5\U0000672c\U00008a9e" // 明示的なUnicodeコードポイント
"\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e" // 明示的なUTF-8バイト
ソースコードがアクセント記号と文字を組み合わせた結合形式など、2つのコードポイントとして文字を表現する場合、ルーンリテラルに配置するとエラーになり(単一のコードポイントではないため)、文字列リテラルに配置すると2つのコードポイントとして表示されます。
解説
文字列リテラルとは何か?
文字列リテラルは、プログラム内に直接書かれた文字列の値です。「こんにちは」「Hello」などのテキストデータを表現します。
たとえ話: ルーンが「1文字」を表すのに対し、文字列は「文章」や「単語」を表します。手紙の内容のようなものです。
package main
import "fmt"
func main() {
// 文字列リテラルの基本
var message string = "こんにちは、世界!"
var name string = "太郎"
fmt.Println(message)
fmt.Println(name)
}
1. 2種類の文字列リテラル
Goには2つの文字列リテラルがあります。
解釈される文字列リテラル(Interpreted string literals)
ダブルクォート(") で囲みます。エスケープシーケンスが解釈されます。
package main
import "fmt"
func main() {
// ダブルクォートで囲む
str1 := "Hello, World!"
str2 := "こんにちは"
str3 := "Line 1\nLine 2" // エスケープが解釈される
fmt.Println(str1)
fmt.Println(str2)
fmt.Println(str3)
// 出力:
// Hello, World!
// こんにちは
// Line 1
// Line 2
}
生文字列リテラル(Raw string literals)
バッククォート(`) で囲みます。エスケープシーケンスは解釈されず、そのままの文字として扱われます。
package main
import "fmt"
func main() {
// バッククォートで囲む
raw1 := `Hello, World!`
raw2 := `こんにちは`
raw3 := `Line 1\nLine 2` // \nはそのまま
fmt.Println(raw1)
fmt.Println(raw2)
fmt.Println(raw3)
// 出力:
// Hello, World!
// こんにちは
// Line 1\nLine 2
}
2. 解釈される文字列リテラル(Interpreted)
基本的な使い方
package main
import "fmt"
func main() {
// 通常のテキスト
str := "Hello, 世界!"
fmt.Println(str)
// ダブルクォートを含める場合はエスケープ
quote := "彼は\"こんにちは\"と言った"
fmt.Println(quote) // 彼は"こんにちは"と言った
// バックスラッシュを含める場合もエスケープ
path := "C:\\Users\\Documents"
fmt.Println(path) // C:\Users\Documents
}
エスケープシーケンス
ルーンリテラルと同じエスケープシーケンスが使えます。
package main
import "fmt"
func main() {
// 改行
fmt.Println("1行目\n2行目\n3行目")
// タブ
fmt.Println("列1\t列2\t列3")
// バックスラッシュ
fmt.Println("ファイルパス: C:\\Program Files\\")
// ダブルクォート
fmt.Println("彼女は\"ありがとう\"と言った")
// すべてのエスケープシーケンス
fmt.Println("アラート:\a") // ベル音
fmt.Println("バックスペース:\b")
fmt.Println("改行:\n")
fmt.Println("タブ:\t")
}
Unicodeエスケープ
package main
import "fmt"
func main() {
// \u (4桁)
str1 := "\u3042\u3044\u3046" // あいう
fmt.Println(str1)
// \U (8桁)
str2 := "\U0001F600\U0001F389" // 😀🎉
fmt.Println(str2)
// 混在
str3 := "\u65e5本\U00008a9e" // 日本語
fmt.Println(str3)
}
バイトエスケープ
package main
import "fmt"
func main() {
// \x (2桁16進数) - 1バイトを表す
str1 := "\x48\x65\x6C\x6C\x6F" // Hello
fmt.Println(str1)
// \nnn (3桁8進数) - 1バイトを表す
str2 := "\110\145\154\154\157" // Hello
fmt.Println(str2)
// UTF-8の明示的なバイト指定
// 「日」= U+65E5 = UTF-8: E6 97 A5
str3 := "\xe6\x97\xa5"
fmt.Println(str3) // 日
}
バイトエスケープとUnicodeエスケープの違い
package main
import "fmt"
func main() {
// \xFF - 1バイト (255)
byte1 := "\xFF"
fmt.Printf("バイト: % X\n", []byte(byte1)) // FF
// \u00FF - 文字 U+00FF (ÿ) = UTF-8で2バイト
unicode1 := "\u00FF"
fmt.Printf("Unicode: % X\n", []byte(unicode1)) // C3 BF
// 同じ文字を表す別の方法
char1 := "ÿ" // 直接入力
char2 := "\u00FF" // Unicode小
char3 := "\U000000FF" // Unicode大
char4 := "\xc3\xbf" // UTF-8バイト
fmt.Println(char1 == char2) // true
fmt.Println(char2 == char3) // true
fmt.Println(char3 == char4) // true
}
3. 生文字列リテラル(Raw)
基本的な使い方
package main
import "fmt"
func main() {
// エスケープシーケンスは解釈されない
raw := `C:\Users\Documents\file.txt`
fmt.Println(raw) // C:\Users\Documents\file.txt
// バックスラッシュはそのまま
raw2 := `\n\t\r`
fmt.Println(raw2) // \n\t\r
// ダブルクォートもエスケープ不要
raw3 := `彼は"こんにちは"と言った`
fmt.Println(raw3) // 彼は"こんにちは"と言った
}
複数行の文字列
生文字列の最大の特徴は、改行をそのまま含められることです。
package main
import "fmt"
func main() {
// 複数行の文字列
poem := `薔薇は赤い
菫は青い
砂糖は甘い
あなたも甘い`
fmt.Println(poem)
// 出力:
// 薔薇は赤い
// 菫は青い
// 砂糖は甘い
// あなたも甘い
}
正規表現に便利
package main
import (
"fmt"
"regexp"
)
func main() {
// 解釈される文字列だとバックスラッシュを2重にする必要がある
pattern1 := "\\d+\\.\\d+" // 数字.数字
// 生文字列ならバックスラッシュは1つでOK
pattern2 := `\d+\.\d+`
fmt.Println(pattern1) // \d+\.\d+
fmt.Println(pattern2) // \d+\.\d+
// 正規表現の使用例
re := regexp.MustCompile(`\d+\.\d+`)
fmt.Println(re.FindString("価格は3.14円です")) // 3.14
}
JSONやHTMLの記述
package main
import "fmt"
func main() {
// JSON (生文字列が便利)
json := `{
"name": "太郎",
"age": 25,
"city": "東京"
}`
fmt.Println(json)
// HTML
html := `<!DOCTYPE html>
<html>
<head>
<title>サンプルページ</title>
</head>
<body>
<h1>こんにちは、世界!</h1>
<p>これはサンプルです。</p>
</body>
</html>`
fmt.Println(html)
}
SQLクエリの記述
package main
import "fmt"
func main() {
// 複雑なSQLクエリ
query := `
SELECT
u.id,
u.name,
u.email,
COUNT(o.id) as order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.created_at >= '2024-01-01'
GROUP BY u.id, u.name, u.email
ORDER BY order_count DESC
LIMIT 10
`
fmt.Println(query)
}
4. キャリッジリターンの扱い
生文字列リテラルでは、**キャリッジリターン(\r)**は自動的に削除されます。
package main
import "fmt"
func main() {
// Windows形式の改行 (CRLF: \r\n)
raw := `Line1
Line2` // エディタによっては\r\nになる可能性がある
// \rは削除され、\nだけが残る
fmt.Printf("% X\n", []byte(raw))
// 解釈される文字列では明示的に指定可能
interpreted := "Line1\r\nLine2"
fmt.Printf("% X\n", []byte(interpreted))
}
5. 同じ文字列の様々な表現
「日本語」という文字列を様々な方法で表現できます。
package main
import "fmt"
func main() {
// すべて同じ文字列を表す
// 方法1: 直接入力(UTF-8)
str1 := "日本語"
// 方法2: 生文字列
str2 := `日本語`
// 方法3: Unicodeコードポイント(\u)
str3 := "\u65e5\u672c\u8a9e"
// 方法4: Unicodeコードポイント(\U)
str4 := "\U000065e5\U0000672c\U00008a9e"
// 方法5: UTF-8バイト列
str5 := "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e"
// すべて等しい
fmt.Println(str1 == str2) // true
fmt.Println(str2 == str3) // true
fmt.Println(str3 == str4) // true
fmt.Println(str4 == str5) // true
// バイト列で確認
fmt.Printf("% X\n", []byte(str1)) // E6 97 A5 E6 9C AC E8 AA 9E
fmt.Printf("% X\n", []byte(str5)) // E6 97 A5 E6 9C AC E8 AA 9E
}
6. 結合文字とコードポイント
アクセント記号などの結合文字は、2つのコードポイントで表現されることがあります。
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
// é (単一コードポイント)
str1 := "\u00e9" // é (U+00E9)
// e + ´ (2つのコードポイント)
str2 := "e\u0301" // e (U+0065) + ´ (U+0301)
fmt.Println("str1:", str1) // é
fmt.Println("str2:", str2) // é
// 見た目は同じだが...
fmt.Println("等しい?", str1 == str2) // false
// ルーン数が異なる
fmt.Println("str1のルーン数:", utf8.RuneCountInString(str1)) // 1
fmt.Println("str2のルーン数:", utf8.RuneCountInString(str2)) // 2
// ルーンリテラルでは2つのコードポイントは使えない
// var r rune = 'e\u0301' // エラー!
}
7. 実用例
例1: ファイルパスの扱い
package main
import "fmt"
func main() {
// Windowsパス(解釈される文字列)
path1 := "C:\\Users\\太郎\\Documents\\file.txt"
// Windowsパス(生文字列 - 推奨)
path2 := `C:\Users\太郎\Documents\file.txt`
fmt.Println(path1)
fmt.Println(path2)
// Unixパス(バックスラッシュ不要)
path3 := "/home/taro/documents/file.txt"
fmt.Println(path3)
}
例2: テンプレート文字列
package main
import "fmt"
func main() {
// メールテンプレート
template := `
件名: ご注文確認
%s 様
この度はご注文いただき、ありがとうございます。
ご注文内容:
- 商品名: %s
- 数量: %d
- 金額: %d円
発送まで今しばらくお待ちください。
株式会社サンプル
`
email := fmt.Sprintf(template, "山田太郎", "Go言語入門", 1, 3000)
fmt.Println(email)
}
例3: ログメッセージ
package main
import (
"fmt"
"time"
)
func main() {
// 構造化ログ
logMessage := fmt.Sprintf(
`{"timestamp":"%s","level":"INFO","message":"ユーザーがログインしました","user_id":12345}`,
time.Now().Format(time.RFC3339),
)
fmt.Println(logMessage)
}
例4: 文字列連結
package main
import (
"fmt"
"strings"
)
func main() {
// + 演算子
str1 := "Hello, " + "World!"
fmt.Println(str1)
// strings.Join
parts := []string{"Go", "is", "awesome"}
str2 := strings.Join(parts, " ")
fmt.Println(str2)
// strings.Builder (効率的)
var builder strings.Builder
builder.WriteString("こん")
builder.WriteString("にち")
builder.WriteString("は")
str3 := builder.String()
fmt.Println(str3)
}
8. 文字列の操作
長さの取得
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
str := "こんにちは"
// バイト数
byteLen := len(str)
fmt.Println("バイト数:", byteLen) // 15 (1文字3バイト × 5文字)
// ルーン(文字)数
runeLen := utf8.RuneCountInString(str)
fmt.Println("文字数:", runeLen) // 5
}
部分文字列の取得
package main
import "fmt"
func main() {
str := "Hello, 世界!"
// バイト単位のスライス(注意が必要)
sub1 := str[0:5] // "Hello"
fmt.Println(sub1)
// ルーン単位で扱う(推奨)
runes := []rune(str)
sub2 := string(runes[0:5]) // "Hello"
sub3 := string(runes[7:9]) // "世界"
fmt.Println(sub2, sub3)
}
文字列の走査
package main
import "fmt"
func main() {
str := "Go言語"
// バイト単位(非推奨)
for i := 0; i < len(str); i++ {
fmt.Printf("%d: %X\n", i, str[i])
}
fmt.Println()
// ルーン単位(推奨)
for i, r := range str {
fmt.Printf("%d: %c (U+%04X)\n", i, r, r)
}
}
まとめ: 文字列リテラルで覚えておくべきこと
2種類の文字列リテラル
| 種類 | 記号 | エスケープ | 改行 | 用途 |
|---|---|---|---|---|
| 解釈される文字列 | "..." | 解釈される | 不可 | 通常の文字列 |
| 生文字列 | `...` | 解釈されない | 可能 | パス、正規表現、複数行 |
エスケープシーケンス
"\n" // 改行
"\t" // タブ
"\\" // バックスラッシュ
"\"" // ダブルクォート
"\u3042" // Unicode (4桁)
"\U0001F600" // Unicode (8桁)
"\xFF" // バイト (16進数)
"\377" // バイト (8進数)
使い分けのガイドライン
package main
import "fmt"
func main() {
// 通常のテキスト → 解釈される文字列
message := "こんにちは、世界!"
// ファイルパス、正規表現 → 生文字列
path := `C:\Users\Documents`
pattern := `\d+\.\d+`
// 複数行テキスト → 生文字列
html := `
<html>
<body>Hello</body>
</html>
`
// エスケープが必要 → 解釈される文字列
quoted := "彼は\"こんにちは\"と言った"
fmt.Println(message, path, pattern, html, quoted)
}
実用的なアドバイス
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
// 文字列は不変(immutable)
str := "Hello"
// str[0] = 'h' // エラー!
// 変更するには新しい文字列を作る
newStr := "h" + str[1:]
fmt.Println(newStr) // hello
// 文字数を数えるときはutf8パッケージを使う
text := "こんにちは"
fmt.Println("文字数:", utf8.RuneCountInString(text))
// 文字列の走査はrangeを使う
for _, r := range text {
fmt.Printf("%c ", r)
}
fmt.Println()
}
文字列リテラルは、Goプログラミングの基本です。解釈される文字列と生文字列を適切に使い分けることで、読みやすく保守しやすいコードが書けます!
おわりに
本日は、Go言語の言語仕様について解説しました。

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

コメント