Character vs Unicode.Scalar vs UTF-8 code unit?

1.9.2 · L2 · Learning

Predict first

What does this print?

let family = "👨‍👩‍👧‍👦"
print(family.count)
  • 1
  • 3
  • 5

Mental model. Scalars are the alphabet, a Character is a written word, and bytes are the ink. Different layers, different counts.

Unicode scalar — one code point (roughly, one “letter” in the Unicode table). Character — a grapheme cluster: one or more scalars that a human reads as one character. UTF-8 code unit — one byte of the encoded form.

For "👨‍👩‍👧‍👦": 1 Character, 7 scalars (four people + three ZWJ), 25 UTF-8 bytes. All correct at the same time.

Four people joined by three zero-width joiners are one grapheme cluster, so one Character, even though it is 7 scalars and 25 UTF-8 bytes.

Code

let family = "👨‍👩‍👧‍👦"
print(family.count)                  // Characters
print(family.unicodeScalars.count)   // scalars
print(family.utf8.count)             // bytes
1, 7, 25

Common traps

  • Using count for a network payload size. Use data.count or string.utf8.count.
  • Truncating by byte count. You can split a scalar in half and produce invalid UTF-8. Truncate on grapheme or scalar boundaries.

They might follow up with

  • What is a combining character? A scalar that modifies the previous one (accents, skin tones). It merges into one grapheme cluster.
  • What is normalization? Different scalar sequences can be canonically equivalent (“é” as one scalar vs “e” + accent). Swift compares canonically equivalent strings as equal.

Related questions

  • 1.9.1 Why is String not a RandomAccessCollection of Character? Why is string[i + 1] not O(1)?
  • 1.9.3 How do you compare strings for UI versus for identifiers?

← Why is String not a RandomAccessCollection of Character? Why is…   How do you compare strings for UI versus for identifiers? →