Strings & runes
Go strings are immutable read-only slices of bytes. The strings, strconv, unicode/utf8, and fmt packages cover almost everything — once you know the difference between a byte and a rune, the language stops surprising you.
Bytes vs runes, builder, common ops
EXAMPLE
package main
import (
"fmt"
"strconv"
"strings"
"unicode"
"unicode/utf8"
)
// 1) Strings ARE byte slices — immutable
func main() {
s := "héllo"
fmt.Println(len(s)) // 6 — BYTE length (é is 2 bytes in UTF-8)
fmt.Println(utf8.RuneCountInString(s)) // 5 — RUNE (character) count
fmt.Printf("%T\n", s[0]) // uint8 (byte)
fmt.Printf("%c\n", s[0]) // h (byte happens to be ASCII here)
}
// 2) Iterating — for-range gives runes
func main2() {
for i, r := range "héllo" {
fmt.Printf("index %d: %c (U+%04X)\n", i, r, r)
}
// index 0: h (U+0068)
// index 1: é (U+00E9)
// index 3: l (U+006C) <- index jumps by 2 bytes
// ...
}
// 3) Indexing by byte vs by rune
s := "héllo"
fmt.Println(s[1]) // 195 — first byte of 'é' (don't slice in the middle of a rune)
runes := []rune(s) // copy into a rune slice for index-based work
fmt.Println(string(runes[1])) // é
// 4) strings.Builder — efficient concat
var b strings.Builder
for i := 0; i < 100; i++ {
fmt.Fprintf(&b, "%d,", i)
}
result := b.String()
// Avoids repeated allocations vs s += ... in a loop.
// 5) Joining + splitting
strings.Join([]string{"a", "b", "c"}, "-") // "a-b-c"
strings.Split("a-b-c", "-") // ["a", "b", "c"]
strings.SplitN("a-b-c-d", "-", 2) // ["a", "b-c-d"]
strings.Fields(" the quick brown ") // ["the", "quick", "brown"] (whitespace split)
// 6) Searching
strings.Contains("hello", "ell") // true
strings.HasPrefix("hello", "he") // true
strings.HasSuffix("hello", "lo") // true
strings.Index("hello", "ll") // 2
strings.Count("banana", "a") // 3
strings.Replace("foo bar foo", "foo", "baz", 1) // "baz bar foo"
strings.ReplaceAll("foo bar foo", "foo", "baz") // "baz bar baz"
// 7) Case + trimming
strings.ToUpper("héllo") // HÉLLO
strings.ToLower("HéLLO") // héllo
strings.Title("hello world") // deprecated; use cases.Title in golang.org/x/text/cases
strings.TrimSpace(" hi ") // "hi"
strings.Trim(" hi ", " ") // "hi"
strings.TrimPrefix("abc.json", "abc.") // "json"
strings.TrimSuffix("abc.json", ".json") // "abc"
// 8) Conversions — strconv
strconv.Itoa(42) // "42"
strconv.Atoi("42") // (42, nil)
strconv.ParseInt("42", 10, 64) // (int64(42), nil)
strconv.ParseFloat("3.14", 64) // (3.14, nil)
strconv.FormatFloat(3.14, 'f', 2, 64) // "3.14"
strconv.Quote("hi \"there\"") // "\"hi \\\"there\\\"\""
strconv.Unquote(`"hi"`) // ("hi", nil)
// 9) fmt formatting verbs
fmt.Sprintf("%d items", 5) // "5 items"
fmt.Sprintf("%5d", 5) // " 5"
fmt.Sprintf("%-5d|", 5) // "5 |"
fmt.Sprintf("%05d", 5) // "00005"
fmt.Sprintf("%.2f", 3.14159) // "3.14"
fmt.Sprintf("%q", "hi") // `"hi"` (Go-quoted)
fmt.Sprintf("%v", []int{1,2,3}) // "[1 2 3]"
fmt.Sprintf("%+v", struct{ A, B int }{1, 2}) // "{A:1 B:2}"
fmt.Sprintf("%#v", struct{ A, B int }{1, 2}) // "struct { A int; B int }{A:1, B:2}"
// 10) Bytes <-> string
b := []byte("hello")
s := string(b)
// Both conversions COPY (string is immutable).
// For zero-copy in hot paths, use 'unsafe' (advanced) — rarely needed.
// 11) Unicode helpers
unicode.IsLetter('A') // true
unicode.IsDigit('7') // true
unicode.IsSpace(' ') // true
unicode.ToUpper('é') // É
// 12) ToValidUTF8 — clean up bad input
strings.ToValidUTF8("hello\xff", "?") // replaces invalid bytes
// 13) Multi-line strings
block := `line1
line2
indented`
// Raw string literal (backticks) — no escapes, includes the newlines.
// 14) Common bugs
// • Slicing s[1:3] on multi-byte text → cuts in the middle of a rune
// Use []rune(s) for index-based work, or for-range for byte-safe iteration
// • len(s) on emoji or CJK → BYTE length, not character count
// • strings.Title for capitalisation — deprecated; use golang.org/x/text/cases
// • Concatenating with + in a loop → quadratic time; use strings.Builder
// • Comparing strings case-insensitively → strings.EqualFold(a, b)
// • Reading UTF-16 (Windows registry, some APIs) → use golang.org/x/text/encoding
// • Treating bytes.Buffer / strings.Builder identically — Builder is write-only
// • Misuse of strconv.Atoi for big numbers → use ParseInt with bitsize 64
Why it matters
Strings are byte slices in Go. Iterate with for ... range to get runes (code points), reach for []rune(s) when you need index-based character access, and use strings.Builder for repeated concatenation. The strings, strconv, and unicode packages cover almost everything else.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Discussion
Loading…