Chapter 4

Conditionals and Loops

A programmer goes out to buy some milk.
Their partner calls them, “While there are eggs, grab a dozen.”
The programmer never returns.

Rust has a few ways to control the flow of your program, which you already know from other languages.

if / else / else if

The syntax will look familiar:

if x > 0 {
    println!("positive");
} else if x < 0 {
    println!("negative");
} else {
    println!("zero");
}

A few details matter here:

for Loops

A for loop can walk through a range of numbers, the elements of an array, or the items in a collection. Rust supports all of these through iterators, but you do not need to understand iterators yet to use the loop. For example:

for i in 0..5 {            // 0, 1, 2, 3, 4
    println!("{i}");
}

for word in ["hi", "rust"] {
    println!("{word}");
}

0..5 is a range: a value that produces the integers from 0 up to (but not including) 5. The inclusive form is 0..=5, which also yields 5. Ranges also work as patterns in match, such as 1..=10 => ....

For larger collections, you'll usually iterate over a Vec, a slice, a HashMap, or the result of s.chars(). For now, "anything you can put on the right of for x in ..." is enough.

while and loop

while runs as long as a condition is true:

let mut n = 10;
while n > 0 {
    println!("{n}");
    n -= 1;
}

loop runs forever, until you break out of it. It is useful when the exit condition is not a simple boolean check at the top, or when you only know whether to stop after doing some work:

let mut attempts = 0;
loop {
    attempts += 1;
    if try_connect() { break; }
    if attempts > 10 { break; }
}

loop can also produce a value: pass an expression to break and the whole loop evaluates to it.

let answer = loop {
    let guess = read_guess();
    if guess == 42 { break guess; }
};

break and continue

Both keywords control the innermost loop:

for n in 0..10 {
    if n % 2 == 1 { continue; }   // skip odd
    if n > 6     { break; }       // stop at 8
    println!("{n}");              // 0, 2, 4, 6
}

Picking the Right Loop

When you need to pick one:

If you are unsure, start with for when there is already a collection or range to walk through. Ranges, slices, and collections all fit this syntax because each can produce an iterator.

Ferris's Mood

Ferris the crab is a creature of simple needs. Two things determine his mood on any given day: how hungry he is (on a 0..=10 scale) and how many naps he's managed to fit in.

Implement ferris_mood(hunger, naps) returning a &'static str, following these rules:

ConditionMood
hunger >= 8"Hangry"
hunger >= 5 and naps == 0"Grumpy"
naps >= 3"Sleepy"
anything else"Content"

&'static str just means "a borrowed string slice that lives for the whole program". String literals like "Hangry" are baked into your compiled binary, so the text is around for as long as the program is running. The 'static lifetime is just the compiler's way of saying "this reference will never dangle." If you've written C, it's the same intuition as a const char * pointing at a string literal. For now, the only thing to take away is "string literals are always safe to return as &'static str."

The "Grumpy" rule needs both parts to be true. Rust spells this && (logical AND). Its sibling || is logical OR. Both short-circuit: if the left side already decides the answer, the right side isn't evaluated.

Order matters. Rust checks an if/else if/else chain from top to bottom and stops at the first match. If you put the naps check before the hunger check, a hungry crab who happens to have napped a lot will get classified as "Sleepy" instead of "Hangry". The tests deliberately include cases (like ferris_mood(9, 5)) that only pass with the right ordering.

Exercise 1 of 4
Open in Web Editor

Results

    Compiler / runtime output
    
                
    Stuck? Show a hint No spoilers, just a nudge
    1. An if/else if chain checks conditions from top to bottom; the first match wins. Follow the order in the rule table.
    2. The "Grumpy" rule needs both conditions to be true. Combine them with && (logical AND).
    Reveal the full solution Spoiler: the complete answer
    /// Ferris the crab has moods. Decide which one based on how hungry he is (a
    /// `0..=10` scale) and how many naps he's had today.
    ///
    /// The rules, in plain English:
    ///
    /// - If Ferris is very hungry (8 or more), he's `"Hangry"`, no matter how
    ///   many naps he's had.
    /// - Otherwise, if he's also a bit hungry (5 or more) and has had no naps,
    ///   he's `"Grumpy"`.
    /// - Otherwise, if he's had three or more naps, he's `"Sleepy"`.
    /// - Otherwise, he's `"Content"`.
    ///
    /// One `if`/`else if`/`else` chain, returning a `&'static str`. The order of
    /// the branches matters; the tests include cases where more than one condition
    /// is true.
    fn ferris_mood(hunger: u32, naps: u32) -> &'static str {
        if hunger >= 8 {
            "Hangry"
        } else if hunger >= 5 && naps == 0 {
            "Grumpy"
        } else if naps >= 3 {
            "Sleepy"
        } else {
            "Content"
        }
    }
    
    #[test]
    fn content_by_default() {
        assert_eq!(ferris_mood(3, 1), "Content");
        assert_eq!(ferris_mood(0, 2), "Content");
    }
    
    #[test]
    fn grumpy_when_hungry_and_napless() {
        assert_eq!(ferris_mood(5, 0), "Grumpy");
        assert_eq!(ferris_mood(7, 0), "Grumpy");
    }
    
    #[test]
    fn sleepy_after_too_many_naps() {
        assert_eq!(ferris_mood(2, 3), "Sleepy");
        assert_eq!(ferris_mood(0, 10), "Sleepy");
    }
    
    #[test]
    fn hangry_overrides_everything() {
        // Ferris is too hungry to care about anything else. If you check naps
        // before hunger, these will fail.
        assert_eq!(ferris_mood(8, 0), "Hangry");
        assert_eq!(ferris_mood(9, 5), "Hangry");
        assert_eq!(ferris_mood(10, 100), "Hangry");
    }
    

    Factorial with a `for` Loop

    n! is 1 * 2 * 3 * ... * n. By convention, 0! == 1. Compute it with a for loop.

    Rust's start..end excludes the end, while start..=end includes it. Choose the bounds for the factors you need. Any binding you update during the loop needs mut; Rust bindings are immutable by default.

    Exercise 2 of 4
    Open in Web Editor

    Results

      Compiler / runtime output
      
                  
      Stuck? Show a hint No spoilers, just a nudge
      1. Use a mutable accumulator.
      2. What starting value works for multiplication and also makes 0! correct?
      Reveal the full solution Spoiler: the complete answer
      /// Returns `n!` (n factorial). By convention, `factorial(0) == 1`.
      ///
      /// Build it up with a `mut` accumulator and a `for` loop over the inclusive
      /// range `1..=n`. For `n == 0` the loop body never runs, so the initial value
      /// carries through unchanged.
      fn factorial(n: u32) -> u32 {
          let mut result = 1;
          for i in 1..=n {
              result *= i;
          }
          result
      }
      
      #[test]
      fn test_factorial() {
          assert_eq!(factorial(0), 1);
          assert_eq!(factorial(1), 1);
          assert_eq!(factorial(2), 2);
          assert_eq!(factorial(5), 120);
          assert_eq!(factorial(10), 3_628_800);
      }
      

      Counting Evens with `for` and `continue`

      The parameter here is a &[i32], a slice: a borrowed view over a sequence of i32 values that live somewhere else. A for loop over the slice gives you a reference to each number in turn. In for number in numbers, number has type &i32. You may also see for &number in numbers: the &number pattern matches the reference and copies the i32 into number. It doesn't change the slice; integers are Copy. Keep a counter and bump it whenever the number is even.

      When the current number is odd, you can use continue to skip straight to the next one. You can then increment the counter without nesting that line inside another if.

      Exercise 3 of 4
      Open in Web Editor

      Results

        Compiler / runtime output
        
                    
        Stuck? Show a hint No spoilers, just a nudge
        1. let mut count = 0u32; plus a for n in numbers loop. The suffix 0u32 pins the integer type so you don't need a separate annotation.
        2. for n in numbers over a &[i32] yields &i32. The % operator accepts the reference, so you can write n % 2. continue skips the rest of the current iteration.
        Reveal the full solution Spoiler: the complete answer
        /// Counts how many numbers in the slice are even.
        ///
        /// A `for` loop over the slice plus a `mut` counter is a simple way to do this.
        /// Use `continue` to skip the odd numbers if you like; it's not required, just
        /// easier to read.
        fn count_evens(numbers: &[i32]) -> u32 {
            let mut count = 0;
            for &number in numbers {
                if number % 2 == 0 {
                    count += 1;
                }
            }
            count
        }
        
        #[test]
        fn test_count_evens() {
            assert_eq!(count_evens(&[]), 0);
            assert_eq!(count_evens(&[1, 3, 5]), 0);
            assert_eq!(count_evens(&[2, 4, 6, 8]), 4);
            assert_eq!(count_evens(&[1, 2, 3, 4, 5, 6]), 3);
            assert_eq!(count_evens(&[0, -2, -3, 7]), 2);
        }
        

        Counting Digits with `while`

        Return the number of decimal digits in n, using a while loop. 0 has one digit, 9 has one, and 10 has two.

        Unlike the range in factorial, a while condition is checked again after each iteration. Your loop needs to make progress toward a stopping point. Integer division drops the remainder: 47 / 10 is 4.

        Exercise 4 of 4
        Open in Web Editor

        Results

          Compiler / runtime output
          
                      
          Stuck? Show a hint No spoilers, just a nudge
          1. What happens to the decimal digits when you divide an integer by 10? Trace it with 100 and decide what each iteration should count.
          2. You can make a mutable working copy with let mut remaining = n; without changing the signature. What should happen for a single-digit input, including zero?
          Reveal the full solution Spoiler: the complete answer
          /// Returns the number of decimal digits in `n`. `digit_count(0)` is `1`.
          ///
          /// Use a `while` loop. The "divide by 10 until you hit zero" pattern is the
          /// classic solution for this kind of "I don't know how many iterations up
          /// front" problem.
          fn digit_count(n: u32) -> u32 {
              let mut count = 1;
              let mut remaining = n;
              while remaining >= 10 {
                  remaining /= 10;
                  count += 1;
              }
              count
          }
          
          #[test]
          fn test_digit_count() {
              assert_eq!(digit_count(0), 1);
              // Boundary check: 10 has two digits, not one.
              assert_eq!(digit_count(10), 2);
              assert_eq!(digit_count(99), 2);
              assert_eq!(digit_count(100), 3);
              assert_eq!(digit_count(1_000_000), 7);
              assert_eq!(digit_count(u32::MAX), 10);
          }
          

          Wrapping Up Conditionals and Loops

          You used each kind of control flow for a slightly different job. The mood classifier chose one branch, the two for loops walked through values you already had, and the while loop kept going until there was nothing left to divide.

          What We Learned

          • if/else is an expression, not just a statement. It can sit on the right of let, be returned from a function, or appear anywhere a value is expected. Both branches must have the same type.
          • Conditions are bool expressions written without surrounding parentheses. Rust does not quietly treat an integer or a string as true or false.
          • for x in iter is the type of loop to use when you already have something to walk through. Ranges (0..n, 0..=n), slices, vectors, and most other collections all work here.
          • while cond runs as long as the condition is true. Reach for it when the iteration count depends on values computed inside the loop (like "divide until zero").
          • loop runs forever until you break. It can also produce a value: let x = loop { ...; break value; };.
          • break exits the innermost loop; continue skips to the next iteration. In count_evens, continue handled the odd numbers first and kept the counter outside a nested if.
          • An accumulator lets you compute one value from many inputs. You start with a value, update it once per loop iteration, and return it when the loop is done. Iterator methods such as sum, count, and fold cover many of the same jobs.
          Next chapter 5Functions

          Optional Chapters

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

          ← Return to the Rust course curriculum