Swift DISCUSSION

Why can I not index a Swift String with an Int, and which count is the real length?

Started by emadfadaee Swift String indexinggrapheme clustersutf8 countString.IndexUnicode
5 replies 248 views 6 participants
Latest activity · 30 Sep 2026

Why can I not index a Swift String with an Int, and which count is the real length?

emadfadaee Swift Forum
#1

I am building a command string to send over Bluetooth and need two things: the character at position 3 and the length of the string in bytes. text[3] does not compile, and text.count does not match the number of bytes that arrive at the other end once the text contains accented letters or emoji.

Why does Swift make string indexing this awkward, and what is the correct way to get a character by position and the byte length?

Community replies 5

Re: Why can I not index a Swift String with an Int, and which count is the real length?

#2

A Swift String is a collection of Character values, and a Character is an extended grapheme cluster: what a reader sees as one character, however many Unicode scalars or bytes it takes. Those clusters have variable size in storage, so finding the n-th one means walking from the start. Swift does not offer an Int subscript because it would look like an O(1) operation while being O(n).

count is the number of grapheme clusters, and computing it is also a walk over the string.

Re: Why can I not index a Swift String with an Int, and which count is the real length?

#3

A concrete case: the word "café" can be stored with a single precomposed é, or as a plain e followed by the combining acute accent, written "cafe\u{301}". Both display the same and compare equal with ==. For the second form, count is 4, unicodeScalars.count is 5, utf16.count is 5 and utf8.count is 6.

Emoji stretch this further: a family emoji built from four people joined by zero-width joiners is 1 Character, 7 scalars, 11 UTF-16 units and 25 UTF-8 bytes.

Re: Why can I not index a Swift String with an Int, and which count is the real length?

#4

For the wire you want the UTF-8 view. text.utf8.count is the byte length, and Array(text.utf8) or Data(text.utf8) gives the bytes to send. Use that number for length fields and for checking against packet size limits, never count.

If the protocol is ASCII-only, validate rather than assume: text.allSatisfy { $0.isASCII } tells you whether the character count and byte count are guaranteed to agree.

Re: Why can I not index a Swift String with an Int, and which count is the real length?

#5

To get a character by position, build an index: let i = text.index(text.startIndex, offsetBy: 3), then text[i]. It traps if the offset is past the end, so for untrusted positions use the variant with limitedBy:, which returns nil instead. For the common cases there are simpler tools: text.first, text.last, text.prefix(3), text.dropFirst(2) and text.suffix(4).

If you need repeated random access, convert once with let chars = Array(text); after that chars[3] is O(1).

Re: Why can I not index a Swift String with an Int, and which count is the real length?

#6

Slicing returns a Substring, not a String. It shares the storage of the original, which makes slicing cheap, but it also keeps the whole original string alive for as long as the slice exists. Convert with String(slice) before storing it long term or passing it to an API that expects String.

One more mismatch to know about: NSString and APIs based on NSRange count UTF-16 units. Mixing text.count with an NSRange length works for plain ASCII and breaks on the first emoji, so convert ranges with the dedicated initialisers rather than with integer arithmetic.

TEP COMMUNITY