【Golang】Unicode上複数コードからなる文字をruneで扱う場合の挙動
検証環境
Mac OS Catalina 10.15.6
Go1.15.6
tl;dr
Unicode上でn個のコードからなる1文字を []rune
に変換すると、[]rune
スライス長は n
になります。
前置き
Goにおける文字列型 string
の値は、 []byte
スライスの値としても扱うことができます。
例えば以下のように string
を []byte
にキャスト可能な他、
s := "こんにちは、Golang" b := []byte(s) // []byte配列用にmemory allocate され、バイト列もcopyされる
string
の値に要素アクセスすると対応する位置の byte
値を取得します。
s := "こんにちは、Golang" b := s[0] // 227
文字列の長さを len
関数で取得すると、 []byte
スライスとしての長さが返ってきます。
s := "こんにちは、Golang" length := len(s) // 24
しかし実際には、本来の文字列一文字一文字についてアクセスしたい場合が多いと思います。(例えば こんにちは、Golang
を12文字の「意味のある文字の配列」として扱いたい、など)
Golangでは rune
型を使用し、この問題を解消します。
rune
文字列を []byte
スライスとして扱う場合、それは元の各文字に割り当てられたUnicode(ISO 10646)をそれぞれUTF-8 encodingに従いバイト列に変換したものになります。
s := "こんにちは、Golang" b := s[0] fmt.Println(b) // -> 227 fmt.Printf("%v\n", []byte("こ")) // -> [227 129 147] fmt.Printf("%+q\n", "こ") // -> "\u3053" (Unicode) fmt.Printf("%q\n", "こ") // -> "12371" (Unicode code point)
Unicodeの単位で文字列の各文字にアクセスするには、文字列を []rune
スライスにキャストします。
s := "こんにちは、Golang" r := []rune(s) fmt.Println(r[0]) // -> 12371 fmt.Println(string(r[0])) // -> こ
あるいは for
で文字列に対して range
による要素アクセスを実行すると、文字列要素に rune
単位でアクセスすることができます。
s := "こんにちは、Golang" for _, r := range s { fmt.Print(string(r)) } // -> こんにちは、Golang
複数コードからなる文字をruneとして扱う場合
先程「Unicodeの単位」と述べましたが、Unicodeには複数のcodeで一つの文字を表現するパターンもあります。 tech.sanwasystem.com
例えば、日本語の漢字のうち微妙に表現が異なる漢字郡は「異体字セレクタ」として扱われます。異体字セレクタでは同じ意味の似た文字に対して同じコードを与え、さらにそれらを識別するためのコードも与えることで(計2つ)、一意な文字を表現します。
以下の例は2つのcodeからなる漢字 邊󠄆
( U+908A U+E0106
)を []rune
に変換したものです。この漢字、右上の「自」が「白」になっているパターンの「わた」なんですがブログ上の表記ではわからないかもです(上記文献を参照)。
wata := "邊󠄄" rWata := []rune(wata) fmt.Printf("%+q\n", rWata) // -> ['\u908a' '\U000e0104'] fmt.Println(len(rWata)) // -> 2
出力例のように、異体字セレクタ文字列を []rune
に変換すると。長さ2となることがわかります。
fmt.Println(rWata[0]) // 37002 fmt.Println(string(rWata[0])) // -> 邊󠄄 fmt.Println(string(rWata[1])) // -> (空文字) fmt.Printf("%+q\n",rWata[0]) // -> '\u908a'
先頭の文字を出力すると、ベースとなる文字が出現します。
このように文字列を rune
で扱う場合も、Unicode上で複数codeからなる文字を扱う場合には想定する文字列長と異なってしまうので注意が必要です。