Strings revisited: the strings package

You have been using strings.ToUpper and strings.ToLower since the Packages lesson. They come from the strings package, Go's standard-library toolbox for everyday string manipulation. This lesson tours the ten or so operations you will reach for constantly.

Case transformation

strings.ToUpper("hello")   // "HELLO"
strings.ToLower("HELLO")   // "hello"

The rest of this lesson covers the operations you have not met yet.

Contains, HasPrefix, HasSuffix

Three boolean checks for "is this piece in the string":

strings.Contains("hello world", "world")   // true
strings.Contains("hello world", "goodbye") // false

strings.HasPrefix("main.go", "main")       // true
strings.HasSuffix("main.go", ".go")        // true

Contains looks anywhere; HasPrefix and HasSuffix are specific to the start and the end. File-extension and URL-scheme checks usually reach for one of the last two.

TrimSpace (and friends)

Strip leading and trailing whitespace:

strings.TrimSpace("   hello  \n")   // "hello"

The strings package has more surgical trims (TrimLeft, TrimRight, Trim, TrimPrefix, TrimSuffix) when you need to strip a specific string or set of characters. For the everyday "clean up user input" case, TrimSpace is enough.

Split, Join, Fields

Split a string into pieces, glue pieces back together, or split on any whitespace:

parts := strings.Split("a,b,c", ",")     // ["a", "b", "c"]
joined := strings.Join(parts, " | ")       // "a | b | c"

Split returns a []string of pieces. Join does the reverse: takes a slice and a separator, returns a string. Use them as a pair when you want to normalise or transform separators.

strings.Fields("  hello world  foo")   // ["hello", "world", "foo"]

Fields is a specialised Split that treats any run of whitespace as a single separator and drops leading/trailing whitespace. That is what you want for tokenising a line of free-form text, because you do not have to filter empty strings out of the result.

Replace and ReplaceAll

Replace occurrences of a substring:

strings.ReplaceAll("banana", "a", "o")    // "bonono"
strings.Replace("banana", "a", "o", 2)    // "bonona"   (only the first 2)

ReplaceAll replaces every occurrence. Replace takes a count if you want to cap it; a negative count also means "replace all".

Count

How many times does a substring appear:

strings.Count("banana", "a")    // 3
strings.Count("banana", "na")   // 2

Handy for quick stats on a piece of text.

A small pipeline

Here is one program that chains several of the operations above. It lowercases a line, splits it into tokens on whitespace, and counts how many tokens start with the letter g:

line := "Go is great, Rust is fast, Generics help"

lower := strings.ToLower(line)
tokens := strings.Fields(lower)

goish := 0
for _, t := range tokens {
    if strings.HasPrefix(t, "g") {
        goish++
    }
}

fmt.Println(goish)   // 3   (go, great, generics)

Each step does one small thing; the complexity is in how the steps combine, not in any single function.

Task

Starter declares sentence := "The quick brown fox jumps over the lazy dog". In main:

  • Split sentence into words with strings.Fields. Print the number of words (the length of the resulting slice).
  • Print the sentence lowercased with strings.ToLower.
  • Print the result of strings.Contains(sentence, "fox"). fmt.Println will print it as true or false.

Expected output:

9
the quick brown fox jumps over the lazy dog
true
Expected output
9
the quick brown fox jumps over the lazy dog
true