😎

【Go】stringの長さには気をつけよう

2025/02/13に公開

文字列の長さを求める時はlenではなくutf8.RuneCountInStringを使う!!

うまくいかなかったプログラムと改善

以前の実装

func Left(stc string, cutLength int) string {
    if len(src) <= cutLength {
        return src
    }
    result := make([]byte, cutLength)
    copy(result, src)

    return (string)(result)
}

改善後

func Left(stc string, cutLength int) string {
    if utf8.RuneCountInString(src) <= cutLength {
        return src
    }
    return string([]rune(src)[:cutLength])
}

lenの問題点

以下のコードで求められる結果は何でしょうか?

eng := "a"
println("len = ", len(eng))

何当たり前のことを聞いてんだと思うかもしれませんが、もちろん出力は以下の通りでlen = 1となります。

len = 1

では、こちらはどうでしょうか?

jpn := "あ"
println("len = ", len(jpn))

英語が日本語に変わっただけ。
だからこれも先ほどと同様で、len = 1だと思った方もいらっしゃるかもしれません。
しかし、結果は以下の通りでlen = 3となります。

len = 3

これはなぜかというと、lenはバイト数を計測しているからです。
日本語のバイト数は基本的に1文字 = 3バイトとされています。
そのため、len("あ")は3という結果を導いた訳です。
私たちが求める結果は、len = 1です。
これでは、文字列の長さを求めるときに困ってしまいます。
そこで最適な関数がutf8.RuneCountInStringです。

utf8.RuneCountInStringとは

func RuneCountInString(s string) (n int)
この関数は引数sに含まれるruneの数を返すという関数です。
utf8パッケージに実装されています。
https://pkg.go.dev/unicode/utf8#RuneCountInString

ここでruneについてですが、runeはコードポイントのことを表します。
runeを説明する上でコンピュータの仕組みを少し解説します。

コンピュータはビット列でデータを表現することができますが、私たちが普段使うものは漢字や英語、ひらがな等のような文字です。
そのため、文字をビット列に変換(符号化)する必要があります。
ここで支えているものがUnicodeです。
Unicodeとは、世界中の文字や記号を表現するために割り当てられた対応表みたいなものです。
例えば、""という文字はUnicodeで"3042"となります。
この"3042"がコードポイント、つまりruneになるわけです。
GoではコードポイントをUTF-8で符号化しているため、最終的にコンピュータが読み取る数値は
e38182となります。

以上より、"あ"のrune数は1となるわけです!

まとめ

  1. lenはバイト数を計測
  2. 日本語文字は基本的に1文字=3バイトとなるため、len ≠ 文字数
  3. 文字数を計測したいなら、utf8.RuneCountInStringを用いる

Discussion