# Strings and Runes

> Source: https://learn-go.org/strings-and-runes/
> Part of Learn Go, free to read.

```go
s := "Hello, 世界"
fmt.Println(len(s))          // 13, not 9 — that is BYTES
```

A Go string is an immutable sequence of **bytes**, conventionally holding UTF-8. `len` gives you the byte count, and indexing gives you a byte:

```go
fmt.Println(s[0])            // 72 — a byte, not "H"
fmt.Println(string(s[0]))    // "H"
fmt.Println(s[7])            // 228 — the first byte of 世, meaningless alone
```

That is the source of the bug: slicing a string at an arbitrary byte offset can cut a multi-byte character in half.

## Runes

A `rune` is an alias for `int32` and holds one Unicode code point.

```go
fmt.Println(utf8.RuneCountInString(s))    // 9 — actual characters

runes := []rune(s)
fmt.Println(len(runes))                    // 9
fmt.Println(string(runes[7]))              // 世
```

Converting to `[]rune` allocates and copies, so do it when you need character-level indexing, not in a hot loop.

## Ranging over a string

`range` decodes UTF-8 for you, which is almost always what you want:

```go
for i, r := range s {
    fmt.Printf("%d: %c\n", i, r)
}
// i is the BYTE index, r is a rune
// 0: H ... 7: 世  10: 界
```

Note the index jumps from 7 to 10 — `世` occupies three bytes. Ranging is the safe way to walk a string; `for i := 0; i < len(s); i++` walks bytes and will split characters.

## Immutability

```go
s[0] = 'h'          // compile error: cannot assign
```

Strings cannot be modified. To change one, build a new one:

```go
b := []byte(s)
b[0] = 'h'
s = string(b)
```

Both conversions copy. That is why concatenating in a loop is expensive.

## Building strings

```go
// BAD: quadratic — each += allocates and copies the whole string
var out string
for _, w := range words {
    out += w + " "
}

// GOOD
var b strings.Builder
b.Grow(64)                     // optional, avoids regrowth
for _, w := range words {
    b.WriteString(w)
    b.WriteByte(' ')
}
out := b.String()

// BEST when you already have the slice
out := strings.Join(words, " ")
```

This is the most common performance mistake in generated Go — see [the performance page](/review/performance/).

## The strings package

```go
strings.Contains(s, "ell")          // true
strings.HasPrefix(s, "Hello")       // true
strings.HasSuffix(s, "界")           // true
strings.Index(s, "o")               // 4, or -1
strings.Split("a,b,c", ",")         // []string{"a","b","c"}
strings.Join([]string{"a","b"}, "-")// "a-b"
strings.TrimSpace("  hi  ")         // "hi"
strings.Trim("xxhixx", "x")         // "hi"
strings.ReplaceAll("a-b-c", "-", "+")
strings.ToUpper(s)
strings.Fields(" a  b ")            // []string{"a","b"} — splits on any whitespace
strings.EqualFold("Go", "GO")       // true — case-insensitive compare
strings.Cut("key=value", "=")       // "key", "value", true
```

`strings.Cut` is the modern way to split on the first occurrence and is clearer than `Index` plus slicing.

## Converting to and from numbers

```go
n, err := strconv.Atoi("42")               // string -> int
s := strconv.Itoa(42)                      // int -> string
f, err := strconv.ParseFloat("3.14", 64)
b, err := strconv.ParseBool("true")
s := strconv.FormatInt(255, 16)            // "ff"
```

:::warn `string(65)` does not do what you expect
```go
string(65)              // "A" — interprets 65 as a code point
strconv.Itoa(65)        // "65" — what you almost certainly wanted
```
`go vet` flags the conversion. Use `strconv` for numbers and `fmt.Sprintf` for anything composite.
:::

## Formatting

```go
fmt.Sprintf("%s is %d years old", name, age)
fmt.Sprintf("%.2f", 3.14159)        // "3.14"
fmt.Sprintf("%v", someStruct)       // default representation
fmt.Sprintf("%+v", someStruct)      // with field names
fmt.Sprintf("%#v", someStruct)      // Go syntax
fmt.Sprintf("%T", someValue)        // the type
fmt.Sprintf("%q", "hi")             // `"hi"` — quoted
```

`%+v` is the one to remember for debugging structs, and `%T` for "what actually is this".

## Exercise

```go
package main

import "fmt"

func main() {
	phrase := "Go is 简单"
	// Print, one per line:
	//   the byte length
	//   the rune count
	//   each rune with its byte index, using range
	//   the phrase reversed BY RUNE (not by byte)
	fmt.Println(phrase)
}
```

## Common questions

### Why is `len()` giving me the wrong length?

It is giving you the byte length, which differs from the character count for any non-ASCII text. Use `utf8.RuneCountInString` for characters, and `range` to iterate them safely.

### When should I use `[]byte` instead of `string`?

When you are building or mutating data, doing I/O, or working with a package that expects bytes. Strings are immutable so every modification copies; `[]byte` avoids that. Converting between them copies, so pick one representation and stay in it within a hot path.

### Is `strings.Builder` worth it for a few concatenations?

No — for two or three, `+` is clearer and the difference is irrelevant. It matters inside loops, where `+=` is quadratic because each step copies everything accumulated so far.
