thecodex.expert · The Codex Family of Knowledge
Go

Strings and Runes

Indexing a Go string with s[i] gives you a single byte, not a character — for multi-byte UTF-8 text that’s rarely what you actually want.

Go 1.26 UTF-8 by convention Last verified:
Canonical Definition

A Go string is an immutable, read-only slice of bytes; by convention (not enforcement) that byte sequence holds UTF-8 encoded text. A rune (an alias for int32) represents a single Unicode code point. Because UTF-8 encodes a code point in anywhere from 1 to 4 bytes, indexing a string with s[i] gives you one raw byte, which is only ever the whole character for plain ASCII text.

Indexing gives bytes, not characters

Because a string is a byte slice underneath, s[i] yields a single byte (a uint8) at that byte offset — for ASCII text this happens to match each character, but for any multi-byte UTF-8 character it splits it apart.

Gostrings.go
s := "café"          // é is 2 bytes in UTF-8
fmt.Println(len(s))    // 5 — byte length, not character count (4 characters)
fmt.Println(s[3])      // 195 — a raw byte, half of é's 2-byte encoding, not 'é'

range over a string gives runes

Unlike plain indexing, for range over a string decodes the UTF-8 correctly: it gives you the byte offset where each rune starts, and the decoded rune itself (as an int32 code point) — this is the idiomatic way to iterate character-by-character.

Gorange_string.go
for i, r := range "café" {
    fmt.Printf("%d: %c\n", i, r)
}
// 0: c
// 1: a
// 2: f
// 3: é      — correctly decoded as ONE rune, even though it's 2 bytes

fmt.Println(len([]rune("café")))   // 4 — the actual character count

Converting between strings, bytes, and runes

Converting a string to []byte gives the raw UTF-8 bytes; converting to []rune decodes it into Unicode code points, which is what you want when you need an accurate character count or to index by character rather than by byte. Both conversions copy the data, since strings are immutable.

Goconversions.go
s := "héllo"
b := []byte(s)     // raw UTF-8 bytes, len 6
r := []rune(s)     // decoded code points, len 5 — the real character count
back := string(r)  // convert back to a string: "héllo"

Sources

1
The Go Authors. "Strings, bytes, runes and characters in Go", go.dev/blog/strings — the canonical explanation by Rob Pike.