💬

Goで文字列の長さが欲しい時はruneを使え

2023/03/21に公開

はじめに

文字型についてあまり考えてこなかったのですが、一度復習しようと思い、まとめてみます。

len()で返すものとは?

普段何気なく使っていた、 len()
文字列を引数に実行すると、その文字列のバイト数を返すようです。
https://pkg.go.dev/builtin#len

例えば、文字 "a"は1バイト(8bit)で表すことのできる情報です。これを len("a") で表示してみると、

main.go
package main

import "fmt"

func main() {
	fmt.Printf("length 'a' is %d\n", len("a"))
}

// 出力
// length 'a' is 1

文字 "a"のバイト数は1バイトであることが分かります。

さらに文字を増やして、文字列 "abc" とすると?

fmt.Printf("length 'a' is %d\n", len("abc"))

// 出力
// length 'a' is 3

もちろん3バイトになりますね。

ただし、日本語の場合は、1文字=1バイトではありません。

fmt.Printf("length 'あ' is %d\n", len("あ"))

// 出力
// length 'あ' is 3

日本語の場合は1文字=3バイト(UTF-8)で表現されるのです。

文字列の長さが欲しい時

len()が文字列のバイト数を返すと言うことは、これでは文字列の中に1文字あたり2バイト以上の文字が含まれていた場合、文字数を正しく返すことができなくなくなります。
例えば、 "あいうえお"という文字列の文字数は5文字ですが、これをlen()を使って文字数を数えて返してもらおうとすると誤りになります。

fmt.Printf("length 'あいうえお' is %d\n", len("あいうえお"))

// 出力
// length 'あいうえお' is 15

ではどうすれば良いのでしょう?
このような場合のために、Goでは rune型と呼ばれる型が用意されています。

rune型とは?

Goではint32型のエイリアスとしてrune型が標準で用意されています。
文字列をrune型に一度キャストしてから、その長さを数えてあげることで、日本語を含む文字列であってもその文字数を正しく数えることができます。

fmt.Printf("length 'あいうえお' is %d\n", len([]rune("あいうえお")))

// 出力
// length 'あいうえお' is 5

これは、UTF-8が1~4バイトで1文字を表現しており、1番長いバイト列は4バイト=すなわち、8bit x 4byte=32bitが必要なためです。
この32bitごとに区切って数えてあげれば、文字数も正しく数えられるというわけです。

終わりに

日本語を含む文字列で、 文字数が5文字じゃなかったら~みたいなロジック絶対書きそうになってたと思うので、事前に知れてよかったです。
Unicode, UTF-8, コードポイントとかまだちょっと理解甘いのでその辺りの記事も書きたいと思います。

参考

https://go.dev/blog/strings

Discussion