Go String Length: Bytes, Runes and Invalid UTF-8
Published · Updated
In Go, len(s) measures a string's bytes. utf8.RuneCountInString(s) counts decoded runes, but it does not prove that the bytes are valid UTF-8. Check utf8.ValidString(s) separately when the input contract requires UTF-8. A rune count is also different from a grapheme count.
Compare length and validity on the same input
The Go specification defines string length in bytes. These seven cases were run in the official Go Playground on Go 1.27.3. The first column shows interpreted Go string literals: their escapes specify the input bytes or code points, rather than extra backslash characters to count.
| Go string literal | Bytes | Runes | Valid UTF-8 |
|---|---|---|---|
"A" | 1 | 1 | true |
"\u00e9\U0001f600" | 6 | 2 | true |
"e\u0301" | 3 | 2 | true |
"\xffA" | 2 | 2 | false |
"\ufffd" | 3 | 1 | true |
"\xe2\x82" | 2 | 2 | false |
"" | 0 | 0 | true |
The accented letter plus emoji occupies six UTF-8 bytes and two runes. The decomposed accent row contains e followed by U+0301: two code points, even though Unicode grapheme segmentation groups that sequence as one grapheme. Counting runes does not apply that grouping.
Why malformed bytes still contribute to the count
A Go string can contain arbitrary bytes; it is not required to contain UTF-8 text. The Go project's explanation of strings makes this distinction explicit. In the utf8 package, rune counting treats erroneous and short encodings as width-one runes. That is why both the invalid ff 41 sequence and the truncated e2 82 sequence produce a count of two.
ValidString answers the separate validity question. A nonzero rune count, or a count that seems plausible, is not a substitute for that check. Decide how to handle rejected input before treating its count as a length of valid text.
Distinguish a decoding error from a literal replacement character
utf8.DecodeRuneInString returns a rune and its byte width. At the start of "\xffA", the observed result was U+FFFD with width one. For the valid literal "\ufffd", it was U+FFFD with width three. The rune value alone cannot distinguish these cases. The empty string returns U+FFFD with width zero, so the nonempty-input qualification matters when interpreting an error result.
Use bytes for a rule about this string's bytes, runes for a rule about decoded code points, and a separate segmentation method for graphemes. These checks do not establish a universal field maximum or the size of a later serialized request.
The String Length guide compares units across tools. For composed and decomposed accents, see Unicode normalization and length before changing the input sequence.