What indexing a string gives you
A Go string is a read-only sequence of bytes. Square brackets with an index pick one of those bytes, counting from 0. In "Avocado" the letter A is at index 0, and the last letter, o, is at index 6, because the string has 7 bytes.
The important detail is the type of the result. fruit[0] is a byte, which is another name for uint8: a small number, not a piece of text. The byte for A is the number 65. Print it directly and you see the number:
fruit := "Avocado"
fmt.Println(fruit[0]) // 65
fmt.Println(string(fruit[0])) // A
Wrapping the byte in string(...) converts it to a one-character string, and that is what you print when you want to see the letter.
Exercise step by step
The lesson starts with fruit := "Avocado" and asks for the first and last characters.
- The first character. Index 0 holds it. Write
fruit[0], wrap it instring(...)so it prints as a letter, and pass that tofmt.Println. - The last character. A string of length 7 has valid indexes 0 to 6, so the last character is at index 6. Do the same thing with
fruit[6].
The solution in Go
fruit := "Avocado"
fmt.Println(string(fruit[0]))
fmt.Println(string(fruit[6]))
It prints each letter on its own line:
A
o
Writing the literal 6 works for this string. For any string, the last index is len(fruit) - 1, which keeps working if the text changes. The next lessons use that form.
Bytes, not characters
For plain English text, one byte is one character, so indexing behaves the way you expect. Go strings are UTF-8, though, and a letter such as é takes two bytes. Indexing returns a single byte of it, not the whole letter:
h := "héllo"
fmt.Println(len(h)) // 6
fmt.Println(h[1]) // 195
fmt.Println(string(h[1])) // Ã
The byte at index 1 is only half of é, and converting it to a string produces an unrelated character. To work with letters rather than bytes, convert the string to a slice of runes first. A rune is a Unicode code point, and []rune(h) has one element per letter:
r := []rune(h)
fmt.Println(len(r)) // 5
fmt.Println(string(r[1])) // é
Looping with for i, r := range h also decodes whole runes. In that loop the index i is the byte position where each rune starts, so after é at position 1 the next letter is at position 3. For this lesson, ASCII text and plain indexing are enough.
Common mistakes
- Printing without
string(...).fmt.Println(fruit[0])prints65. This is the most common surprise. - Using index 7. The length is 7, but indexes stop at 6. Reading
fruit[7]stops the program with a run-time panic (index out of range). - Counting from 1. The first character is at index 0, not 1.
- Negative indexes. Go has no
fruit[-1]for "the last one". A constant negative index is a compile error, and a negative index computed at run time panics. Uselen(fruit) - 1. - Assigning to an index.
fruit[0] = 'a'does not compile, because strings can't be changed in place.
Why a number and not a character
Go separates bytes from text on purpose. A string may contain letters from any alphabet, and a letter can span several bytes, so Go doesn't pretend that s[i] is "the i-th letter". It hands you the raw byte and leaves the decision to you. Converting with string(...) is correct here because every byte of "Avocado" is a whole ASCII letter. You can also compare a byte directly with a character literal, as in fruit[0] == 'A', which is true.
Key takeaways
- Indexes start at 0, and the last valid index is
len(s) - 1. s[i]is abyte, a number. Usestring(s[i])to print it as a letter.- Indexing works on bytes. For text beyond ASCII, convert to
[]runeor userange.