ぼく 「あれ?」
str := "0123456"
runes := []rune(str)
// 範囲内
fmt.Println(string(runes[0])) // 0
fmt.Println(string(runes[3])) // 3
fmt.Println(string(runes[4:6])) // 45
fmt.Println(string(runes[4:7])) // 456
// 範囲外、、のはずだけど
fmt.Println(string(runes[4:8])) // 456 <= エラー出ないのか、、
fmt.Println(string(runes[10])) // エラー: index out of range <= わかる
fmt.Println(string(runes[10:20])) // (空文字が出力)
ぼく 「範囲外のインデックス指定してるんだから、普通 "xxx out of range" 的なエラーが出るでしょ」
ぼく 「Go言語、まさかスライスで範囲外指定してもいいように察してくれるのか、?」
ぼく 「ここ範囲外だからとりあえず空文字返しとこ、的な」
ぼく 「いやいやいや、、」
ぼく 「stringでやってみよ」
strArray := []string{"0", "1", "2", "3", "4", "5", "6"}
// 範囲内
fmt.Println(strArray[0]) // 0
fmt.Println(strArray[3]) // 3
fmt.Println(strings.Join(strArray[4:6], "")) // 45
fmt.Println(strings.Join(strArray[4:7], "")) // 456
// 範囲外
fmt.Println(strings.Join(strArray[4:8], "")) // エラー: slice bounds out of range
fmt.Println(strArray[10]) // エラー: index out of range
fmt.Println(strings.Join(strArray[10:20], "")) // エラー: slice bounds out of range
ぼく 「普通にエラー出るな、、」
ぼく 「これならわかる、けどさっきの[]runeの範囲外スライスはなんでエラー出ないんだ」
ぼく 「スライスのスライスだからか、、? []runeは確か配列じゃなくてスライスだったな、(※配列の参照、Goには可変長配列がないので代わりにこれをつかう)」
ぼく 「、、stringスライスのスライスならどうだろ」
var strSlice []string
strSlice = append(strSlice, "0", "1", "2", "3", "4", "5", "6")
fmt.Println(strings.Join(strSlice[4:8], "")) // エラー: slice bounds out of range
fmt.Println(strSlice[10]) // エラー: index out of range
fmt.Println(strings.Join(strSlice[10:20], "")) // エラー: slice bounds out of range
ぼく 「変わらないな、、」
ぼく 「スライスのスライスは範囲外でもエラー出ない、とかいう話ではないのか」
ぼく 「なんでruneのスライスだけ、範囲外でスライスしてもエラー出ないんだろう」
ぼく 「(わからんtaskete)」
追記(2019/07/31) : 親切な方(aimofさん)降臨
ぼく 「神は実在した…(激しく感謝)」
ぼく 「__aimofさん__ありがとうございます」
ぼく 「というか__cap__ってなんだ」
ぼく 「キャップだから…帽子か?」
調べてみた
-
cap ( capacity ) : 配列の容量
▲ 要素数(length)とは異なる
ぼく 「Go言語にはlengthだけじゃなくて、capacityとかあるのか…」
ぼく 「で、__[]rune(str)__でキャストするときには、なんか暗黙的にcapacity大きく取られてたってことか」
ぼく 「みてみよ」
str := "0123456"
runes := []rune(str)
fmt.Println(cap([]byte(str))) // 32
fmt.Println(cap(runes)) // 32
// capacity内
fmt.Println(string(runes[4:32])) // 456
// capacity外
fmt.Println(string(runes[4:33])) // slice bounds out of range
ぼく 「ほんとだ、、runeスライスへのキャストで、だいぶ大きめにcapacity取られてるな」
ぼく 「32もある…」
ぼく 「なんでこの数なんだろ」
(下の方で少し試した(おまけ)ことあります)
とりあえずまとめ
[]runeは範囲外でスライスしてもいいように察してくれる(エラー出ない)それ以外の配列・スライスなら範囲外スライスは普通にエラーがでる- 配列・スライスの__len, capの問題だった__
- makeでcapを指定、とかではなく__[]rune(str)__でキャストすると、capが勝手にある程度の値を取ってくれる
- len (=length) : 配列の要素数 ( ◁ わかる )
- cap (=capacity) : 配列の__容量__ ( ◁ 知らなかった )
- capの範囲内であれば、例え配列の要素が存在せずとも slice bounds out of range は出ない
補足
- string: 文字列を示すbyteスライスのイミュータブル参照、Go言語ではstr[0]とか書くと1文字目ではなくひとつ目のbyte値が得られる
- rune: Unicodeコードポイント、ざっくりいうと文字1つひとつに割り当てられたコード値. runeのスライスはstringに変換できる
追記(2019/07/31) : おまけ
// ▼ 1文字の場合
s := "1"
rs := []rune(s)
// length
fmt.Println(len(rs)) // 1
// capacity ( <= 1文字でもとりあえず32確保する模様)
fmt.Println(cap([]byte(s))) // 32
fmt.Println(cap(rs)) // 32
// ▼ 32文字の場合
st := "12345678901234567890123456789012"
rus := []rune(st)
// length
fmt.Println(len(rus)) // 32
// capacity ( <= 32文字なら、まだcapacityも32のまま)
fmt.Println(cap([]byte(st))) // 32
fmt.Println(cap(rus)) // 32
// ▼ 33文字の場合
str := "123456789012345678901234567890123"
runes := []rune(str)
// length
fmt.Println(len(runes)) // 33
// capacity ( <= 32を超えて初めて増加! )
fmt.Println(cap([]byte(str))) // 48
fmt.Println(cap(runes)) // 36