0xf

日記だよ

golangのrangeと文字列

func main() {
    for i, c := range "hello, こんにちは" {
        fmt.Printf("%d, %c, %s\n", i, c, reflect.ValueOf(c).Kind())
    }
}

まあ、こういうコードがあると思いねえ。

$ go run main.go
0, h, int32
1, e, int32
2, l, int32
3, l, int32
4, o, int32
5, ,, int32
6,  , int32
7, こ, int32
10, ん, int32
13, に, int32
16, ち, int32
19, は, int32

実行結果はこうなるやね。インデックスとしてバイト位置がやってきている。アスキー文字については1バイト、そうでないやつは3バイト使っているのだね。

解せぬ。

// string is the set of all strings of 8-bit bytes, conventionally but not // necessarily representing UTF-8-encoded text. A string may be empty, but // not nil. Values of string type are immutable. type string string

と定義にはあるので、stringは []byte と可換であり UTF-8文字列エンコードされた何者かではない。しかし range が返す結果を見る限りだと、エンコードを考慮して動作しているように見える。[]runeに変換されている? と思ったけど、そうであればインデックスの位置が不自然である。[]rune に変換されていたら、たとえば「ん」のインデックスは8ではないか。

func main() {
    for i, c := range []rune("hello, こんにちは") {
        fmt.Printf("%d, %c, %s\n", i, c, reflect.ValueOf(c).Kind())
    }
}

こうすると、

$ go run main.go
0, h, int32
1, e, int32
2, l, int32
3, l, int32
4, o, int32
5, ,, int32
6,  , int32
7, こ, int32
8, ん, int32
9, に, int32
10, ち, int32
11, は, int32

こうなるわけでな。解せないのは range の挙動です。python__iter__() みたいなのがあってそれを辿るという感じでもないからなあ。

go.dev

For a string value, the "range" clause iterates over the Unicode code points in the string starting at byte index 0. On successive iterations, the index value will be the index of the first byte of successive UTF-8-encoded code points in the string, and the second value, of type rune, will be the value of the corresponding code point. If the iteration encounters an invalid UTF-8 sequence, the second value will be 0xFFFD, the Unicode replacement character, and the next iteration will advance a single byte in the string.

なるほど、仕様として決まっている。rangeは文字列を受け取るとUnicode文字列として扱って処理を進めていく。インデックス値はバイト位置。はい。