Works by

Ren's blog

@rennnosuke_rk 技術ブログです

【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つ)、一意な文字を表現します。

ja.wikipedia.org

以下の例は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からなる文字を扱う場合には想定する文字列長と異なってしまうので注意が必要です。

参考文献

blog.golang.org

tech.sanwasystem.com

ja.wikipedia.org