Chapter 7

Exercise Break: Word Count

Enough syntax for a moment! You know more syntax than you might realize, so let's put it to work. We'll write a small word-count library using strings, for loops, and functions.

Splitting Text into Words

The standard library provides split_whitespace for &str; you can use it in a for loop to walk through each word in a string:

for word in "hello  world\nrust".split_whitespace() {
    println!("{word}"); // hello, world, rust
}

It splits on any run of whitespace (spaces, tabs, newlines) and skips empties, which is what you want for natural text. .split_whitespace() returns an iterator over the words. A for loop consumes that iterator without requiring its concrete type.

Walking Characters

The same idea works at the character level via .chars():

for c in "hi".chars() {
    println!("{c}"); // h, i
}

With .split_whitespace() for words and .chars() for characters, a for loop can count either one.

Counting Words

Start with the smallest piece of the library: given some text, return how many words it contains. For this exercise, words are anything separated by whitespace, so "hello world" has two and " " has none.

Use a for loop rather than an iterator counting method.

Useful from the Standard Library

  • str::split_whitespace walks through every whitespace-separated piece of a string. It handles tabs, newlines, and runs of consecutive spaces without any extra work on your part.
Exercise 1 of 3
Open in Web Editor

Results

    Compiler / runtime output
    
                
    Reveal the full solution Spoiler: the complete answer
    /// Returns the total number of words in `text`. Words are pieces separated by
    /// whitespace, so `"hello world"` has two words.
    fn word_count(text: &str) -> usize {
        let mut count = 0;
        for _ in text.split_whitespace() {
            count += 1;
        }
        count
    }
    
    #[test]
    fn test_word_count_simple() {
        assert_eq!(word_count("hello world"), 2);
    }
    
    #[test]
    fn test_word_count_empty() {
        assert_eq!(word_count(""), 0);
        assert_eq!(word_count("   "), 0);
    }
    
    #[test]
    fn test_word_count_collapses_whitespace() {
        // Multiple spaces, tabs, and newlines all count as a single boundary, so
        // this is four words, not seven.
        assert_eq!(word_count("one  two\tthree\nfour"), 4);
    }
    

    Counting Characters

    You counted characters in the strings chapter. The same catch applies here: text.len() returns bytes, not characters. Rust strings are UTF-8 internally, so a single visible character like é can take more than one byte. text.chars() walks Unicode scalar values (chars), which is what we count here. For "café" that's 5 bytes but 4 characters, and the two only agree on plain ASCII.

    Use a for loop, as in the previous exercise. Count every character, whitespace included, so "hi there" returns 8 (seven letters plus the space). The Unicode test below checks the difference between bytes and characters.

    Useful from the Standard Library

    • str::chars walks through every char in a string, whitespace and all.
    Exercise 2 of 3
    Open in Web Editor

    Results

      Compiler / runtime output
      
                  
      Reveal the full solution Spoiler: the complete answer
      /// Returns the number of characters in `text`. Counts every `char` the string
      /// contains, whitespace included.
      fn char_count(text: &str) -> usize {
          let mut count = 0;
          for _ in text.chars() {
              count += 1;
          }
          count
      }
      
      #[test]
      fn test_char_count_simple() {
          // "hi there" → h, i, ' ', t, h, e, r, e = 8
          assert_eq!(char_count("hi there"), 8);
      }
      
      #[test]
      fn test_char_count_empty() {
          assert_eq!(char_count(""), 0);
      }
      
      #[test]
      fn test_char_count_whitespace_counts() {
          // Whitespace characters are real characters too: 3 spaces + '\n' + '\t'
          // = 5.
          assert_eq!(char_count("   \n\t"), 5);
      }
      
      #[test]
      fn test_char_count_unicode() {
          // `café` is 4 characters even though it's 5 bytes in UTF-8. `text.len()`
          // would say 5; `text.chars().count()` says 4. `"hi café"` → h, i, ' ', c,
          // a, f, é = 7.
          assert_eq!(char_count("café"), 4);
          assert_eq!(char_count("hi café"), 7);
      }
      

      Longest Word

      Return the length (in characters) of the longest word in the text. If the text has no words at all, return 0.

      Use a for loop. What information do you need to remember after examining each word?

      This is the manual version of "max by some property." The iterator version expresses the same search as a one-liner. Doing it once by hand first makes each part of that shortcut recognizable.

      Useful from the Standard Library

      • str::chars and Iterator::count together count Unicode scalar values. Using word.len() would return the byte length, which differs from the character count for accented or non-Latin text.
      Exercise 3 of 3
      Open in Web Editor

      Results

        Compiler / runtime output
        
                    
        Reveal the full solution Spoiler: the complete answer
        /// Returns the length (in characters) of the longest word in `text`. Words are
        /// whitespace-separated. Returns 0 when the text has no words.
        fn longest_word(text: &str) -> usize {
            let mut longest = 0;
            for word in text.split_whitespace() {
                let length = word.chars().count();
                if length > longest {
                    longest = length;
                }
            }
            longest
        }
        
        #[test]
        fn test_longest_word_simple() {
            // "a bb ccc dddd" → 4
            assert_eq!(longest_word("a bb ccc dddd"), 4);
        }
        
        #[test]
        fn test_longest_word_empty() {
            assert_eq!(longest_word(""), 0);
            assert_eq!(longest_word("   "), 0);
        }
        
        #[test]
        fn test_longest_word_unicode() {
            // "café" is 4 characters even though it's 5 bytes in UTF-8. `word.len()`
            // would say 5; `word.chars().count()` says 4.
            assert_eq!(longest_word("hi café"), 4);
        }
        

        Wrapping Up Word Count

        You wrote three functions using a counter or running maximum, a for loop, and a return value.

        What We Learned

        • text.split_whitespace() walks the words in a string for you. It handles any kind of whitespace and skips empty pieces.
        • text.chars() walks every Unicode scalar value in a string, whitespace and all.
        • To track the running maximum, use let mut max = 0; for x in xs { if x > max { max = x; } }.
        • word.chars().count() counts char values, not necessarily visible characters. str::len returns bytes, and the two differ the moment you hit a non-ASCII character.

        What Comes Next

        Iterator methods let you write the same three functions without explicit loops:

        fn word_count(text: &str)    -> usize { text.split_whitespace().count() }
        fn char_count(text: &str)    -> usize { text.chars().count() }
        fn longest_word(text: &str)  -> usize { text.split_whitespace().map(|w| w.chars().count()).max().unwrap_or(0) }
        

        The word-frequencies example extends this from counting all words to recording which words appear and how often.

        Want more practice? Try the optional word count challenge. It does not count toward course completion.

        Next chapter 8Enums and Pattern Matching

        Optional Chapters

        Extra practice to explore at your own pace. These chapters do not count toward course progress.

        ← Return to the Rust course curriculum