0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Go の string と rune 周りのお話し

Posted at

Go で文字列からN番目の文字を抜き出したい際に、以下のようなコードを想像する方は多いのではないでしょうか?

s := "Hello World"
fmt.Println(s[0]) // 72

私もそのうちの一人でした。fmt.Println(s[0]) の値として期待するのは ”72” ではなく “H” です。この挙動は私が業務で使用している node.js とは異なる挙動で気になったので調べてみました。

String とは

In Go, a string is in effect a read-only slice of bytes.

Go において string は read-only な byte のスライスです。

つまり、fmt.Println(s[0]) の値として表示された “72” は byte を表しています。

s := "Hello World"
fmt.Println(s[0]) // 72 <- byte

Goにおいて string は、UnicodeやUTF-8などで定義されたテキスト、その他の定義済みのフォーマット等を保持しているわけではないのです。

Unicode と Code Point

唐突ですが、Unicode と Code Point について少し解説します。

Unicodeは世界で使われる全ての文字を共通の文字集合にて利用可能に使用という考えで作られた標準規格です。

コンピュータ上では、文字は内部的に数値として扱われます。そのため数値が文字をどのように表すのかという規則が必要になります。そこで登場するのが Code Point です。

Unicode では、Code Point とは文字をエンコードした16ビットの数値になります。Code Space と呼ばれる 0 to 10FFFF(16進数)の間の整数で表現されます。

以下の図では、文字(Abstract)と Code Point(Encoded)の対応関係を示しています。

スクリーンショット 2023-08-21 23.40.58.png

引用:https://www.unicode.org/versions/Unicode14.0.0/ch02.pdf#G25564

Rune と Code Point

The Go language defines the word rune as an alias for the type int32

Go では rune は int32 のエイリアスです。

“Code point” is a bit of a mouthful, so Go introduces a shorter term for the concept: rune.

つまり、rune は 先ほどの章で説明した Unicode の Code Point です。

試しに Unicode でエスケープした文字と、 rune にキャストした値を比較しても一致しています。

s := `⌘`
fmt.Printf("%+q\n", s)          // "\u2318"
fmt.Printf("% x", []rune(s)[0]) // 2318 

Single quote, Double quote, Back quote

文字列をリテラルで定義するときに、single quote(’)、double quote(”)、back quote(`) で定義することができるが、以下のような違いがある。

'a'            // rune型(code point なので複数の文字の表現はできない)
"Hello\nWorld" // string型(エスケープシーケンスが解釈される)
`Hello\nWorld` // string型(エスケープシーケンスが解釈されない)

Loop

ひとつずつ byte のスライスを読み込んでいった場合は、byte を取得できます。

s := "Hello World"
for i := 0; i < len(s); i++ {
	fmt.Println(s[i]) // byte
}

range を 使った場合、値には byte ではなく rune が入るので注意が必要です。index には読み込んでいる rune を byte で表現した際の先頭の位置が格納されています。

s := "日本語"
for index, value := range s {
	fmt.Printf("%#U starts at byte position %d\n", value, index)
}

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

おまけ:数値を文字列に変換する関数

以下のことを利用すると、数値を文字列に変換する関数なんかも作れます。

大文字のAを基準に演算することで数値を文字列に変換する関数が以下です。

func IntToCapitalizedChar(i int) string {
    // single quote で囲っているので、A は rune型(Code Point)
    // rune型は数値なので演算可能
    // 'A' から i 番目の rune を計算し、 string 型へキャストする
	return string('A' + i)
}

func main() {
	for i:=0; i<26; i++ {
		fmt.Println(IntToCapitalizedChar(i))
	}  
}

// Output
// A
// ...
// Z

おまけ:N番目の文字列は抜き出せる

冒頭ではN番目の文字列は抜き出せないかのように表現していますが、 string が byte のスライスであることを理解すると文字列の抜き出しが可能なことに気づきます。

Go は UTF-8 であり、日本語の文字列が3バイトであることを利用すると、3バイトずつスライスを読み込むことでN番目の文字を取得できます。

func main() {
	s := "こんにちは"
	for i := 0; i < len(s); i += 3 {
		fmt.Println(s[i:i+3])
	}
}

// Output
// こ
// ん
// に
// ち
// は

まとめ

  • string は read-only な byte のスライスである
  • Unicode の Code Point とは文字をエンコードした際の16ビットの数値
  • rune は Unicode の Code Point であり、 int32 のエイリアスである
  • 文字列をリテラルで定義する際の違い
    • single quote(’): rune 型
    • double quote(”): string型(エスケープシーケンスが解釈される)
    • back quote(`): string型(エスケープシーケンスが解釈されない)
  • loop 処理は range を使うと、値に byte ではなく rune が渡される
0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?