Table of Contents
- Strings, Runes & Bytes
- Contents
- The three types
- UTF-8 fundamentals
- Indexing vs ranging
- Conversions
- strings package
- strings.Builder
- strconv package
- bytes package
- fmt verbs
Strings, Runes & Bytes¶
Contents¶
- The three types
- UTF-8 fundamentals
- Indexing vs ranging
- Conversions
- strings package
- strings.Builder
- strconv package
- bytes package
- fmt verbs
The three types¶
| Type | Element | Mutable | Use for |
|---|---|---|---|
string |
bytes (immutable) | no | text, map keys, comparisons |
[]byte |
uint8 |
yes | I/O, building, mutation |
[]rune |
int32 (code point) |
yes | per-character logic |
s := "héllo" // immutable, UTF-8 encoded
len(s) // 6 (bytes, not characters — é is 2 bytes)
utf8.RuneCountInString(s) // 5 (characters)
Strings are immutable: s[0] = 'x' is a compile error.
UTF-8 fundamentals¶
Go source is UTF-8; string literals are UTF-8 byte sequences. A rune is one Unicode code point (1–4 bytes in UTF-8).
import "unicode/utf8"
s := "€"
len(s) // 3 (bytes)
utf8.RuneCountInString(s) // 1
r, size := utf8.DecodeRuneInString(s) // r='€', size=3
utf8.ValidString(s) // true
'A' // rune literal = 65
'世' // rune literal = 19990
"é" == "é" // true
Indexing vs ranging¶
s := "héllo"
s[0] // byte 'h' (104), type byte
s[1] // byte 0xC3 — first half of 'é'!
// Indexing gives BYTES. Ranging gives RUNES with byte offsets:
for i, r := range s {
fmt.Printf("%d:%c ", i, r) // 0:h 1:é 3:l 4:l 5:o (note index jump)
}
// Convert to []rune for random character access:
rs := []rune(s)
rs[1] // 'é'
string(rs[1:]) // "éllo"
Conversions¶
b := []byte("hi") // string -> []byte (copies)
s := string(b) // []byte -> string (copies)
rs := []rune("héllo") // string -> []rune
s2 := string(rs) // []rune -> string
string(65) // "A" (rune->string), vet warns; prefer:
string(rune(65)) // "A"
strconv.Itoa(65) // "65" (int->decimal string)
string([]byte{104, 105}) // "hi"
Each conversion typically allocates and copies. In hot loops, work in []byte and avoid round-tripping.
strings package¶
import "strings"
strings.Contains("seafood", "foo") // true
strings.ContainsRune("abc", 'b') // true
strings.HasPrefix("go.mod", "go") // true
strings.HasSuffix("a.go", ".go") // true
strings.Index("chicken", "ken") // 4 (-1 if absent)
strings.Count("cheese", "e") // 3
strings.Replace("oink", "o", "0", -1) // "0ink" (-1 = all)
strings.ReplaceAll("aaa", "a", "b") // "bbb"
strings.Split("a,b,c", ",") // ["a" "b" "c"]
strings.SplitN("a,b,c", ",", 2) // ["a" "b,c"]
strings.Fields(" a b ") // ["a" "b"] (split on whitespace)
strings.Join([]string{"a","b"}, "-") // "a-b"
strings.ToUpper("go"); strings.ToLower("GO")
strings.Title("hi there") // deprecated; use cases.Title
strings.TrimSpace(" hi ") // "hi"
strings.Trim("xxhixx", "x") // "hi"
strings.TrimPrefix("v1.2", "v") // "1.2"
strings.TrimSuffix("a.go", ".go") // "a"
strings.Repeat("ab", 3) // "ababab"
strings.EqualFold("Go", "GO") // true (case-insensitive)
strings.Map(func(r rune) rune {...}, s) // transform runes
before, after, found := strings.Cut("k=v", "=") // "k","v",true (Go 1.18+)
r := strings.NewReader("data") // io.Reader over a string
strings.Builder¶
Efficient string concatenation — avoids quadratic copying of += in loops.
var b strings.Builder
b.Grow(64) // optional capacity hint
for i := 0; i < 3; i++ {
fmt.Fprintf(&b, "%d,", i)
b.WriteString("x")
b.WriteByte('\n')
b.WriteRune('€')
}
result := b.String() // no extra copy
b.Len() // current size
b.Reset() // reuse
Do not copy a strings.Builder after first use (it holds an internal []byte).
strconv package¶
import "strconv"
strconv.Itoa(42) // "42"
strconv.Atoi("42") // 42, nil
strconv.ParseInt("ff", 16, 64) // 255, nil (base 16, bitSize 64)
strconv.ParseInt("0x1f", 0, 64) // base 0 = infer from prefix
strconv.ParseFloat("3.14", 64) // 3.14, nil
strconv.ParseBool("true") // true, nil (1,t,T,TRUE,...)
strconv.FormatInt(255, 16) // "ff"
strconv.FormatFloat(3.14, 'f', 2, 64) // "3.14"
strconv.Quote(`a"b`) // "\"a\\\"b\""
strconv.Unquote(`"hi"`) // "hi", nil
strconv.AppendInt(buf, 42, 10) // append without alloc
Atoi/Parse* return a *strconv.NumError on failure; check err.
bytes package¶
Mirror of strings but for []byte, plus a Buffer.
import "bytes"
bytes.Contains(b, []byte("foo"))
bytes.Equal(a, b) // fast byte comparison
bytes.Split(b, []byte(","))
bytes.TrimSpace(b)
bytes.ToUpper(b)
var buf bytes.Buffer // implements io.Reader and io.Writer
buf.WriteString("hello ")
fmt.Fprintf(&buf, "%d", 42)
buf.Write([]byte{'!'})
buf.String() // "hello 42!"
buf.Bytes() // underlying bytes (no copy)
json.NewEncoder(&buf).Encode(v) // common with encoders
fmt verbs¶
type P struct{ X, Y int }
p := P{1, 2}
fmt.Printf("%v\n", p) // {1 2} default
fmt.Printf("%+v\n", p) // {X:1 Y:2} with field names
fmt.Printf("%#v\n", p) // main.P{X:1, Y:2} Go-syntax representation
fmt.Printf("%T\n", p) // main.P the type
fmt.Printf("%d %b %o %x %X\n", 255, 255, 255, 255, 255) // 255 11111111 377 ff FF
fmt.Printf("%c %q %U\n", 65, "hi", '€') // A "hi" U+20AC
fmt.Printf("%f %.2f %e %g\n", 3.14, 3.14159, 3.14, 3.14) // float forms
fmt.Printf("%s %q\n", "hi", "hi") // hi "hi" (%q adds quotes)
fmt.Printf("%p\n", &p) // pointer address
fmt.Printf("%t\n", true) // boolean
fmt.Printf("%5d|%-5d|%05d\n", 42, 42, 42) // 42|42 |00042 (width/align/zero)
fmt.Printf("%+d % d\n", 42, 42) // +42 42 (sign flags)
fmt.Printf("%x\n", []byte("hi")) // 6869 (hex of bytes)
fmt.Printf("%%\n") // literal %
// Errors with %w wrap (see errors.md):
fmt.Errorf("ctx: %w", err)
// Sprint family returns strings; Fprint writes to an io.Writer:
s := fmt.Sprintf("%d-%s", 1, "a")
fmt.Fprintf(os.Stderr, "warn: %v\n", err)
%v on a type implementing fmt.Stringer calls its String() method. %+v/%#v are your debugging best friends.