
こんにちは。よっしーです(^^)
本日は、Go言語の言語仕様について解説しています。
背景
Go言語を学び始めて、公式の「The Go Programming Language Specification(言語仕様書)」を開いてみたものの、「英語で書かれていて読むのが大変…」「専門用語ばかりで何を言っているのかわからない…」と感じたことはありませんか? 実は、多くのGo初心者が同じ壁にぶつかっています。
言語仕様書は、Go言語の「正式な取扱説明書」のような存在です。プログラミング言語がどのように動くのか、どんなルールで書くべきなのかが詳しく書かれていますが、その分、初めて読む人には難しく感じられるのも事実です。
そこでこの記事では、言語仕様書の導入部分を丁寧な日本語訳とともに、初心者の方でも理解しやすい補足説明を加えてお届けします。「強く型付けされている」「ガベージコレクション」「並行プログラミング」といった専門用語も、具体例を交えながらわかりやすく解説していきます。
言語仕様書は難しそうに見えますが、一つひとつの概念を丁寧に読み解いていけば、必ず理解できます。一緒に、Go言語の基礎をしっかり学んでいきましょう!
文字列型との間の変換(Conversions to and from a string type)
- バイトのスライスを文字列型に変換すると、スライスの要素が連続するバイトである文字列が生成される。
string([]byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'}) // "hellø"
string([]byte{}) // ""
string([]byte(nil)) // ""
type bytes []byte
string(bytes{'h', 'e', 'l', 'l', '\xc3', '\xb8'}) // "hellø"
type myByte byte
string([]myByte{'w', 'o', 'r', 'l', 'd', '!'}) // "world!"
myString([]myByte{'\xf0', '\x9f', '\x8c', '\x8d'}) // "🌍"
- ルーンのスライスを文字列型に変換すると、個々のルーン値を文字列に変換して連結した文字列が生成される。
string([]rune{0x767d, 0x9d6c, 0x7fd4}) // "\u767d\u9d6c\u7fd4" == "白鵬翔"
string([]rune{}) // ""
string([]rune(nil)) // ""
type runes []rune
string(runes{0x767d, 0x9d6c, 0x7fd4}) // "\u767d\u9d6c\u7fd4" == "白鵬翔"
type myRune rune
string([]myRune{0x266b, 0x266c}) // "\u266b\u266c" == "♫♬"
myString([]myRune{0x1f30e}) // "\U0001f30e" == "🌎"
- 文字列型の値をバイトのスライス型に変換すると、文字列のバイトが連続する要素である非nilスライスが生成される。結果のスライスの容量は実装依存であり、スライスの長さより大きい場合がある。
[]byte("hellø") // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'}
[]byte("") // []byte{}
bytes("hellø") // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'}
[]myByte("world!") // []myByte{'w', 'o', 'r', 'l', 'd', '!'}
[]myByte(myString("🌏")) // []myByte{'\xf0', '\x9f', '\x8c', '\x8f'}
- 文字列型の値をルーンのスライス型に変換すると、文字列の個々の Unicode コードポイントを含むスライスが生成される。結果のスライスの容量は実装依存であり、スライスの長さより大きい場合がある。
[]rune(myString("白鵬翔")) // []rune{0x767d, 0x9d6c, 0x7fd4}
[]rune("") // []rune{}
runes("白鵬翔") // []rune{0x767d, 0x9d6c, 0x7fd4}
[]myRune("♫♬") // []myRune{0x266b, 0x266c}
[]myRune(myString("🌐")) // []myRune{0x1f310}
- 最後に、歴史的な理由により、整数値を文字列型に変換することができる。この形式の変換は、与えられた整数値を持つ Unicode コードポイントの(マルチバイトの場合もある)UTF-8 表現を含む文字列を生成する。有効な Unicode コードポイントの範囲外の値は
"\uFFFD"に変換される。
string('a') // "a"
string(65) // "A"
string('\xf8') // "\u00f8" == "ø" == "\xc3\xb8"
string(-1) // "\ufffd" == "\xef\xbf\xbd"
type myString string
myString('\u65e5') // "\u65e5" == "日" == "\xe6\x97\xa5"
注意:この形式の変換は将来的に言語から削除される可能性がある。go vet ツールは特定の整数から文字列への変換を潜在的なエラーとしてフラグを立てる。代わりに utf8.AppendRune や utf8.EncodeRune などのライブラリ関数を使用すべきである。
解説
文字列変換の全体像
Go の文字列と他の型の変換は、大きく5つのパターンがあります。日本語を含む多言語テキストを正しく扱うために、これらの違いを理解しておくことが大切です。
[]byte ←→ string ←→ []rune
↑
int(非推奨)
パターン1 & 3:[]byte ↔ string
バイトスライスと文字列の変換は、バイト列をそのままコピーします。
// string → []byte
b := []byte("hello") // [104 101 108 108 111]
// []byte → string
s := string([]byte{104, 101, 108, 108, 111}) // "hello"
日本語のようなマルチバイト文字も、UTF-8 のバイト列としてそのまま扱われます。
b := []byte("こんにちは")
fmt.Println(len(b)) // 15(1文字あたり3バイト × 5文字)
fmt.Println(b) // [227 129 147 227 130 147 ...]
バイトレベルの操作(ファイルI/O、ネットワーク通信など)で頻繁に使う変換です。
パターン2 & 4:[]rune ↔ string
ルーンスライスと文字列の変換は、**文字単位(Unicode コードポイント単位)**で行われます。
// string → []rune
r := []rune("白鵬翔")
fmt.Println(r) // [30333 40300 32724]
fmt.Println(len(r)) // 3(3文字)
// []rune → string
s := string([]rune{30333, 40300, 32724}) // "白鵬翔"
[]byte と []rune の最大の違いは、日本語のような多バイト文字の扱いです。
s := "Go言語"
// バイト単位
b := []byte(s)
fmt.Println(len(b)) // 8("Go" = 2バイト + "言語" = 6バイト)
// 文字単位
r := []rune(s)
fmt.Println(len(r)) // 4(G, o, 言, 語)
文字の追加・削除・置換など、文字単位の操作をしたいときは []rune を使いましょう。
// 文字列の3文字目を取得
s := "こんにちは"
r := []rune(s)
fmt.Println(string(r[2])) // "に"
// []byte だと壊れる
fmt.Println(string(s[2])) // 意味のないバイトが出力される
パターン5:整数 → 文字列(非推奨)
整数を文字列に変換すると、その整数を Unicode コードポイントとして解釈し、対応する文字の文字列が生成されます。
string(65) // "A"(65 は 'A' のコードポイント)
string(0x1F600) // "😀"
ただし、これは非推奨です。 よくある間違いは「数値を文字列表現に変換したい」ケースです。
// やりがちな間違い
string(65) // "A"(数字の "65" ではない!)
// 数値を文字列にしたい場合はこちら
strconv.Itoa(65) // "65"
fmt.Sprintf("%d", 65) // "65"
go vet はこの種の変換を警告します。将来のGoバージョンで削除される可能性があるので、utf8.AppendRune や utf8.EncodeRune を使いましょう。
// 非推奨
s := string(0x266c) // "♬"
// 推奨
buf := utf8.AppendRune(nil, 0x266c)
s := string(buf) // "♬"
カスタム型でも変換できる
原文の例では myByte や myRune といったカスタム型が登場しています。基底型が byte や rune であるカスタム型のスライスも、文字列との変換が可能です。
type myByte byte
type myRune rune
s := string([]myByte{'h', 'i'}) // "hi"
r := []myRune("Hello") // []myRune{72, 101, 108, 108, 111}
変換時のコピーに注意
string → []byte や []byte → string の変換では、データのコピーが発生します。文字列は不変なので、バイトスライスに変換するとき独立したコピーを作らないと、スライス経由で文字列が変更されてしまうからです。
s := "hello"
b := []byte(s) // データがコピーされる
b[0] = 'H' // b を変更しても...
fmt.Println(s) // "hello"(元の文字列は変わらない)
頻繁に変換を繰り返すとパフォーマンスに影響するので、大量のデータを扱う場合は変換の回数を最小限にする工夫が大切です。
nil スライスからの変換
nil のバイトスライスやルーンスライスを文字列に変換すると、空文字列 "" になります。逆に、空文字列をスライスに変換すると、nil ではなく空のスライス(長さ0)になります。
string([]byte(nil)) // ""
string([]rune(nil)) // ""
b := []byte("")
fmt.Println(b == nil) // false(空スライスであって nil ではない)
fmt.Println(len(b)) // 0
この区別は通常気にする必要はありませんが、JSON のシリアライズなどで null と [] の違いが重要になる場面では覚えておくと役立ちます。
おわりに
本日は、Go言語の言語仕様について解説しました。

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

コメント