iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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.

Example

Example
s := "héllo"
for i, r := range s {
    fmt.Printf("%d %c\n", i, r)
}
Try it Yourself »

Discussion

Loading…