len(s)がUTF-8のバイト数を返し文字数と一致しない
Goの文字列はUTF-8のバイト列として保持されており、len(s)は文字数ではなくバイト数を返します。日本語のようなマルチバイト文字を含む文字列では、見た目の文字数より大きな値になります。
なぜエラーが出ないのか
出力: 9
(エラーなし)- Goは何も報告しません。コンパイルも実行も正常に終わるコードだからです
出力: 9- 実際の出力 — 期待した結果と食い違っている箇所
見つけ方- エラーが出ないので、出力を目で確かめるしかありません。この種の誤りが最も発見が遅れます
このエラーが出る典型パターン
パターン1
1 package main 2 3 import "fmt" 4 5 func main() { 6 s := "日本語" 7 fmt.Println(len(s) ) ^ 8 }
「日本語」は3文字ですが、UTF-8では1文字が3バイトなのでlen(s)は9を返します。文字数を数えたいなら[]rune(s)に変換してから数える必要があります。
直し方: len(s) を len([]rune(s)) にします。
パターン2
1 package main 2 3 import "fmt" 4 5 func main() { 6 s := "こんにちは" 7 fmt.Println(len(s) ) ^ 8 }
5文字のひらがなも同様に1文字3バイトなので、len(s)は見た目の文字数の3倍になります。
直し方: len(s) を len([]rune(s)) にします。
パターン3
1 package main 2 3 import "fmt" 4 5 func main() { 6 s := "東京" 7 fmt.Println(len(s) ) ^ 8 }
短い文字列でも同じ規則が適用され、2文字の漢字でもlen(s)は6を返します。
直し方: len(s) を len([]rune(s)) にします。
パターン4
1 package main 2 3 import "fmt" 4 5 func main() { 6 s := "プログラミング" 7 fmt.Println(len(s) ) ^ 8 }
カタカナの7文字でも同様で、len(s)は21というバイト数を返し、見た目の文字数とは一致しません。
直し方: len(s) を len([]rune(s)) にします。
パターン5
1 package main 2 3 import "fmt" 4 5 func main() { 6 s := "猫" 7 fmt.Println(len(s) ) ^ 8 }
1文字だけの文字列でも、len(s)はバイト数の3を返すため、見た目の文字数1とは一致しません。
直し方: len(s) を len([]rune(s)) にします。
よくある誤解
「len(s)は文字列の見た目の文字数を返すはず」という思い込みは誤りです。len(s)は常にバイト数を返すため、文字(rune)単位で数えたい場合はutf8.RuneCountInStringやrune変換したスライスの長さを使う必要があります。
実務での勘所
GoのstringはRustのStringと違い、型として有効なUTF-8であることを保証されているわけではありません。Goの文字列は単なるバイト列で、UTF-8として扱うのはあくまで言語やツールチェイン全体の強い慣習です(for rangeで文字列を回すとUTF-8として解釈しルーンごとに分解してくれますが、これは慣習に基づく機能であって型の保証ではありません)。len(s)が常にバイト数を返すのはこのためで、文字数を数えたいときは自分でutf8.RuneCountInStringのような関数を呼ぶ必要があります。