String と Rune

(初出: はじめての Go 言語 (on Windows) その4 - Qiita

文字列を示す string は不変(immutable)なオブジェクトだが,中身は byte 配列である。 したがって以下のように

package main

import "fmt"

func main() {
    nihongo := "日本語"
    size := len(nihongo)

    fmt.Printf("nihongo = %d bytes :", size)
    for i := 0; i < size; i++ {
        fmt.Printf(" %02x", nihongo[i])
    }
    fmt.Println()
}

string をダンプすると以下の結果になる1

nihongo = 9 bytes : e6 97 a5 e6 9c ac e8 aa 9e

このように(string なんて名前なのに)文字単位で情報を保持しているわけではないため,最初の2文字を取り出すつもりでうっかり

package main

import "fmt"

func main() {
    nihongo := "日本語"

    fmt.Printf("nihongo = %s\n", nihongo)
    fmt.Printf("nippon = %s\n", nihongo[:2])
}

なんてなコードを書くと以下の結果になる。

nihongo = 日本語
nippon = ��

文字列を文字単位で扱うには rune 型を使う2。 いや,ルーンってどんだけ厨二… ゲフンゲフン。

ええつと,たとえばこんな感じ。

package main

import "fmt"

func main() {
    nihongo := "日本語"
    nihongoRune := []rune(nihongo)
    size := len(nihongoRune)

    fmt.Printf("nihongo = %d characters : ", size)
    for i := 0; i < size; i++ {
        fmt.Printf("%#U ", nihongoRune[i])
    }
    fmt.Printf("\n")
}

実行結果はこんな感じになる。

nihongo = 3 characters : U+65E5 '日' U+672C '本' U+8A9E '語' 

または, string に対して for range 構文を使ってループを回すと rune 単位で取得できる。

package main

import "fmt"

func main() {
    nihongo := "日本語"

    for pos, runeValue := range nihongo {
        fmt.Printf("%#U starts at byte position %d\n", runeValue, pos)
    }
}

このコードの実行結果はこんな感じ。

U+65E5 '日' starts at byte position 0
U+672C '本' starts at byte position 3
U+8A9E '語' starts at byte position 6

rune 型の実体は int32 で,内部表現は Unicode の符号点(code point)になっている。 stringrune 配列は相互変換できるので,文字列を切り取る場合は

package main

import "fmt"

func main() {
    nihongo := "日本語"

    fmt.Printf("nihongo = %s\n", nihongo)
    fmt.Printf("nippon = %s\n", string([]rune(nihongo)[:2]))
}

のように string[]runestring と変換していけば安全に処理できる。 上のコードの実行結果はこんな感じ。

nihongo = 日本語
nippon = 日本

もう少し細かい処理が必要なら unicode/utf8 パッケージを使う手もある3

ブックマーク

Go 言語に関するブックマーク集はこちら

参考図書

photo
プログラミング言語Go
アラン・ドノバン (著), ブライアン・カーニハン (著), 柴田芳樹 (著)
丸善出版 2016-06-20 (Release 2021-07-13)
Kindle版
B099928SJD (ASIN)
評価     

Kindle 版出た! 一部内容が古びてしまったが,この本は Go 言語の教科書と言ってもいいだろう。感想はこちら

reviewed by Spiegel on 2021-05-22 (powered by PA-APIv5)


  1. ちなみに Go 言語で取り扱う文字列の文字エンコーディングは UTF-8 が既定である。ソースコードがそもそも UTF-8 を要求しているし(つまりリテラルの文字列はかならず UTF-8 になる), stringrune の相互変換も文字列が UTF-8 であることを前提にしている。しかし,実際には string の実体はただの byte 配列であり,中身が UTF-8 文字列であることを保証しているわけではない。通常は,未知の文字列についてはいったん byte 配列に格納しておいて,何らかの方法で UTF-8 に変換してから string にキャストするのが安全である(または各文字エンコーディング用の独自 type を作るか)。なお,文字エンコーディングの変換については別の記事で改めて紹介する。 ↩︎

  2. 厳密には文字単位ではなく Unicode の符号点単位と言ったほうがいいかもしれない。 Unicode では結合文字(濁点記号や異体字セレクタなど)にもコードポイントが割り当てられているので,特に日本語で文字を意識した文字列操作を行う場合は注意が必要である。ぶっちゃけ面倒くさい。 ↩︎

  3. ちなみに strings パッケージは内部で unicode/utf8 パッケージを使っているようだ。 ↩︎