Strings, bytes and runes

Go · Essentials · 12 min

Indexing and looping over strings behaves differently from many languages because Go strings are UTF-8 bytes.

A Go string is an immutable sequence of bytes, conventionally UTF-8 text. Two consequences matter every day:

  • len(s) is the number of bytes, not characters. len("héllo") is 6 because é takes two bytes.
  • s[i] is a byte, and slicing s[a:b] slices bytes, which can cut a multi-byte character in half.

A rune (alias for int32) is a Unicode code point. for i, r := range s decodes UTF-8 as it goes, giving the byte offset i and the rune r — use it whenever you mean "each character". utf8.RuneCountInString(s) counts runes, and []rune(s) converts to a slice of runes when you need random access by character.

Strings are immutable, so building one with += in a loop allocates repeatedly. Use strings.Builder (or bytes.Buffer) to build text efficiently, and convert with []byte(s) / string(b) when an API wants bytes (each conversion copies).

The strings package covers most text work: Contains, HasPrefix, Split, Fields, Join, Replace, ToUpper, TrimSpace, Cut. Numbers convert with strconv: strconv.Itoa(42), strconv.Atoi("42"), strconv.ParseFloat. Note that string(65) gives "A" (a rune), not "65" — go vet warns about it.

Raw string literals in backquotes, like a regex or a multi-line template, ignore escape sequences.

Example

package main

import (
	"fmt"
	"strconv"
	"strings"
	"unicode/utf8"
)

func main() {
	s := "héllo"
	fmt.Println(len(s), utf8.RuneCountInString(s)) // 6 5

	for i, r := range "hé" {
		fmt.Printf("%d:%c ", i, r) // 0:h 1:é
	}
	fmt.Println()

	var b strings.Builder
	for i := range 3 {
		b.WriteString(strconv.Itoa(i))
	}
	fmt.Println(b.String()) // 012

	key, val, _ := strings.Cut("lang=go", "=")
	fmt.Println(key, val) // lang go
}

Remember

  • Strings are immutable UTF-8 bytes; len counts bytes
  • range over a string yields runes; indexing yields bytes
  • Use strings.Builder for building and strconv for numbers

Watch out for

  • Using string(n) to format an integer — use strconv.Itoa(n) or fmt.Sprint(n).

Check yourself

  1. What is the type of `c` in `for _, c := range "go"`?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: go.dev/blog/strings

Builds on

Unlocks

Nothing builds on this yet.