12. Strings

Strings are the values you print most. Chapter 07 showed the two ways to write one: between double quotes, "like this", and between backticks, `like this`. This chapter covers what Go gives you directly for strings: joining, measuring, reading a single position, cutting out a piece, and writing special characters.

Joining strings with +

You can join two strings with + (the concatenation operator). It gives one new string with the second stuck onto the end of the first:

first := "Hello, "
second := "world!"
fmt.Println(first + second) // Hello, world!

The original variables are untouched — + builds a new string.

Go's + does not convert numbers to strings. Adding a number to a string is a compile error:

level := 7
fmt.Println("Level " + level)
invalid operation: "Level " + level (mismatched types untyped string and int)

The fix for now is fmt.Printf from Chapter 09, which takes numbers directly:

level := 7
fmt.Printf("Level %d complete\n", level) // Level 7 complete

Chapter 14 shows how to turn a number into a string, when you need one.

Length with len

The len function gives a string's length:

fmt.Println(len("hello")) // 5
fmt.Println(len(""))      // 0
greeting := "Welcome"
fmt.Println(len(greeting)) // 7

Use len whenever you need a string's length — for centring text, checking input length, or building borders.

Open exercises/12/01-length.go. Add a line that prints the length of your own name.

Strings are bytes

The last section did not say what len counts. It counts bytes, the small units a computer stores text in. A plain English letter, digit, or space takes one byte, so for text like "hello", bytes and characters come to the same number. Letters with accents take more:

fmt.Println(len("e")) // 1
fmt.Println(len("é")) // 2

To count characters instead of bytes, use RuneCountInString from the unicode/utf8 package. A rune is Go's word for one character:

word := "café"
fmt.Println(len(word))                    // 5
fmt.Println(utf8.RuneCountInString(word)) // 4

This needs a second import. To import more than one package, put them in parentheses, one per line:

import (
    "fmt"
    "unicode/utf8"
)

The full program is exercises/12/03-bytes.go. Most text in this book is plain English letters, so the rest of Part 3 uses len. Keep in mind that it can be larger than the number of characters you see.

Indexing

Each byte in a string has a position, called its index. Square brackets read the byte at one position:

word := "dragon"
fmt.Println(word[0]) // 100

Two surprises here. Indexes start at 0, not 1, so word[0] is the first position. And the result is not the letter d but 100, the number that stores d, because indexing gives you a byte. To get text, use a slice, which is the next section.

Go strings are 0-indexed. If you want the first position, use word[0], not word[1]. Asking for word[1] gives the second one. This will feel wrong at first; it becomes natural quickly.

Slicing

A slice of a string gives you part of it, as a new string. The syntax is s[start:stop], where start is the index of the first byte you want and stop is the index after the last one:

word := "dragon"
fmt.Println(word[0:3]) // dra
fmt.Println(word[3:])  // gon
fmt.Println(word[:1])  // d

Leaving out stop means "to the end of the string", and leaving out start means "from the beginning". So word[:1] is the first letter as a string.

Go has no negative indexes. word[-3:] stops with invalid argument: index -3 (constant of type int) must not be negative. To count from the end, use len:

word := "dragon"
fmt.Println(word[len(word)-3:]) // gon
fmt.Println(word[len(word)-1:]) // n

Asking for a position past the end is an error the compiler usually cannot catch, so it happens while the program runs. The program stops and prints a message such as:

panic: runtime error: slice bounds out of range [:10] with length 6

Slicing counts bytes, like len and indexing. In "café" the é takes two bytes, at indexes 3 and 4. word[:3] gives caf, but word[:4] cuts the é in half and the program prints a broken character. For plain English letters this never comes up.

Open exercises/12/02-index-and-slice.go. A variable holds a word. Using slicing, print just its first letter, then just its last.

Strings cannot be changed

You can give a string variable a new string, but you cannot change a byte inside a string:

word := "dragon"
word[0] = 'D'
fmt.Println(word)
cannot assign to word[0] (neither addressable nor a map index expression)

To get a changed version, build a new string from the pieces and assign it:

word := "dragon"
word = "D" + word[1:]
fmt.Println(word) // Dragon

Escape sequences

Some characters are awkward in a string because they collide with Go's punctuation. The fix is an escape sequence — a backslash \ followed by a character, which Go replaces with the special character:

Sequence What it becomes
\n a newline (move to next line)
\t a tab
\" a literal double quote
\\ a literal backslash

A single quote needs no escape inside double quotes, so write "it's". Writing "it\'s" stops with unknown escape.

Example:

fmt.Println("Line 1\nLine 2\nLine 3")
fmt.Println("Name\tLevel\tHP")
fmt.Println("She said \"hi\" and waved.")

Output:

Line 1
Line 2
Line 3
Name    Level   HP
She said "hi" and waved.

Multi-line strings

When a string spans many lines with lots of quotes, escape sequences get noisy. A string between backticks is a raw string: anything between them is the string, including newlines and quotes, and escape sequences are not replaced.

poem := `
Roses are red,
Violets are blue,
"Pick a number,"
said the program to you.
`

fmt.Println(poem)

Once created, a raw string is just a regular string — a friendlier way to write it. The string starts with a new line and ends with one, because the backticks sit on their own lines. These examples are in exercises/12/04-escapes.go.

Because \n is not replaced in a raw string, `a\nb` prints the four characters a\nb on one line. This makes backticks handy for text with many backslashes, such as a Windows path.

Homework

Problem 1 — Initials

Open exercises/12/homework/01-initials.go. Two variables hold a first and last name. Using slicing, print the person's initials, like K.R. for "Keiko Raharja".

Problem 2 — Stat line

Open exercises/12/homework/02-stat-line.go. Three variables are declared at the top: a string name, an int level, an int hp. Using fmt.Printf, print one line in this exact shape:

Keiko  Lv 7   HP 95

The labels (Lv, HP) and the spacing between fields are literal spaces inside the string you give fmt.Printf.

Problem 3 — One-line three-line poem

Open exercises/12/homework/03-three-line-poem.go. Print a three line poem using exactly one fmt.Println call. The line breaks must come from \n inside the string. Add a // comment after the call explaining why one call can produce three lines.

Challenge — Last letters

Open exercises/12/homework/04-last-letters.go. A variable holds a word. Using a slice and len, print its last three characters, then print its length. Make it work for any plain-letter word of at least three letters without counting by hand.

Stuck or finished? Open the homework solutions page.