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

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

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

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

スポンサーリンク

背景

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

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

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

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

文字列型との間の変換(Conversions to and from a string type)

  1. バイトのスライスを文字列型に変換すると、スライスの要素が連続するバイトである文字列が生成される。
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'})   // "🌍"
  1. ルーンのスライスを文字列型に変換すると、個々のルーン値を文字列に変換して連結した文字列が生成される。
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" == "🌎"
  1. 文字列型の値をバイトのスライス型に変換すると、文字列のバイトが連続する要素である非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'}
  1. 文字列型の値をルーンのスライス型に変換すると、文字列の個々の Unicode コードポイントを含むスライスが生成される。結果のスライスの容量は実装依存であり、スライスの長さより大きい場合がある。
[]rune(myString("白鵬翔"))   // []rune{0x767d, 0x9d6c, 0x7fd4}
[]rune("")                  // []rune{}

runes("白鵬翔")              // []rune{0x767d, 0x9d6c, 0x7fd4}

[]myRune("♫♬")              // []myRune{0x266b, 0x266c}
[]myRune(myString("🌐"))    // []myRune{0x1f310}
  1. 最後に、歴史的な理由により、整数値を文字列型に変換することができる。この形式の変換は、与えられた整数値を持つ 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.AppendRuneutf8.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.AppendRuneutf8.EncodeRune を使いましょう。

// 非推奨
s := string(0x266c)   // "♬"

// 推奨
buf := utf8.AppendRune(nil, 0x266c)
s := string(buf)  // "♬"

カスタム型でも変換できる

原文の例では myBytemyRune といったカスタム型が登場しています。基底型が byterune であるカスタム型のスライスも、文字列との変換が可能です。

type myByte byte
type myRune rune

s := string([]myByte{'h', 'i'})       // "hi"
r := []myRune("Hello")                 // []myRune{72, 101, 108, 108, 111}

変換時のコピーに注意

string[]byte[]bytestring の変換では、データのコピーが発生します。文字列は不変なので、バイトスライスに変換するとき独立したコピーを作らないと、スライス経由で文字列が変更されてしまうからです。

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言語の言語仕様について解説しました。

よっしー
よっしー

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

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

コメント

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