— notes
Runes, Bytes, and Graphemes in Go
I once ran into this problem while handling names in Tamil and emoji in a Go web app: a string that looked short wasn’t, and reversing it produced gibberish.
graph TD
G["Grapheme Cluster\n(what users see)"]
R1["Rune 1\n(code point)"]
R2["Rune 2\n(combining mark)"]
B1["Bytes\n(1-4 per rune)"]
B2["Bytes\n(1-4 per rune)"]
G --> R1
G --> R2
R1 --> B1
R2 --> B21. Bytes
Go represents strings as immutable UTF-8 byte sequences.
s := "வணக்கம்"
fmt.Println(len(s)) // 2121 bytes, not visible symbols. Every Tamil character can span 3 bytes.
2. Runes
string to []rune gives you code points.
rs := []rune(s)
fmt.Println(len(rs)) // 77 runes, but some Tamil graphemes (like “க்”) combine two runes: க + ்.
3. Grapheme clusters
Go’s standard library stops at runes. For visible character units, use github.com/rivo/uniseg.
for gr := uniseg.NewGraphemes(s); gr.Next(); {
fmt.Printf("%q\n", gr.Str())
}That outputs what a human reads: வ, ண, க், க, ம்.
graph LR
T["வணக்கம் (Tamil)"]
T --> |"len()"| BY["21 bytes"]
T --> |"[]rune"| RU["7 runes"]
T --> |"uniseg"| GR["5 graphemes"]Quick reference
| Task | Approach |
|---|---|
| Count code points | utf8.RuneCountInString(s) |
| Count visible units | Grapheme iteration (uniseg) |
| Reverse text | Parse into graphemes, reverse slice, join |
| Slice safely | Only use s[i:j] on grapheme boundaries |