Why s[0] does not work
In many languages the first character of a string is taken by its number: s[0]. In Swift this does not compile:
let text = "café"
print(text[0]) // ✗error: 'subscript(_:)' is unavailable: cannot subscript String with an Int, use a String.Index instead.The compiler says it plainly: you can't ask a string for a character by an Int number; you need an index of the type String.Index. What is it, and what is wrong with a number?
Remember the string of beads. The large beads on it differ in size: one has one small bead under it, another has two, a third has four. To find the third large bead, you can't measure off two equal steps with a ruler. You have to go along the string from the start and count the beads one by one.
Why Swift forbids a number
If every character took the same number of bytes, a character's number would give its place in memory at once. In UTF-8 the sizes differ. To find character number 1000, Swift would have to walk past the 999 characters before it.
The expression s[1000] would look instant but work slowly. In a loop such slow steps would add up without anyone noticing. So Swift doesn't let it compile and offers ways in which this walk is visible in the code.
Interviewers like to ask this: why can't you write s[0] in Swift? A good answer: characters take different numbers of bytes, so a character can be found by its number only by walking from the start of the string. Instead of numbers, Swift uses String.Index indices.
Characters at the edges of a string
Most often you need not a character in the middle but the start or the end of a string. For that a string has ready-made tools:
let word = "Swift"
let empty = ""
print(word.first ?? "?", word.last ?? "?") // S t
print(empty.first ?? "?") // ?
print(word.prefix(3), word.dropFirst()) // Swi wift
print(word.prefix(10)) // Swiftfirst and last
first is the first character of the string, last is the last one. An empty string has neither, so both return the optional Character?: a character or nil. Before printing, the box is opened, here with ??:
word.first ?? "?"givesS, andword.last ?? "?"givest.emptyhas no first character, and??puts in the fallback character"?".
prefix and dropFirst
prefix(3)gives the first three characters:Swi.dropFirst()gives everything except the first character:wift.prefix(10)on five characters returns the whole string and doesn't crash the program.
The result of prefix and dropFirst has the type Substring — a part of the original string. print prints it like an ordinary string. To get a separate String, wrap the result: String(word.prefix(3)).
A string index
A character in the middle of a string is taken by a string index. It is a value of the type String.Index: it remembers the place of a character in the string, not its number. You get an index from the string itself:
let third = word.index(word.startIndex, offsetBy: 2)
print(word[third]) // iindex(_:offsetBy:)
word.startIndexis the place of the first character.word.index(word.startIndex, offsetBy: 2)counts two characters forward from it and returns the index of the third one.word[third]takes the character at this index: the letteri.
The expression is long, and that is the point: the code shows that Swift counts characters from the start of the string.
Here the bead picture is inaccurate. On a string of beads you find the bead you need by eye, but a String.Index can only come from the string itself, for example by counting characters from startIndex.
Common mistake
The code tried to take the last character through endIndex:
let city = "Oslo"
print(city[city.endIndex]) // ✗Fatal error: String index is out of boundsThe program compiles but crashes. endIndex is not the place of the last character but the place right after it, like the end of the string after the last bead. There is no character there. The last character is city.last.
Key points
- A string can't be indexed by a number:
s[0]does not compile, because characters take different numbers of bytes. firstandlastreturnCharacter?,prefix(n)gives the firstncharacters, anddropFirst()gives everything except the first.- A character in the middle is taken by an index:
s[s.index(s.startIndex, offsetBy: 2)]. endIndexpoints past the last character, sos[s.endIndex]crashes the program.