Lecture 9 - Loops

Follow-up from Friday

  • Your papers are up here
  • HW2 is due Wednesday. Please get started soon so we have time to help if you need it!

Office hours updates

Ava - Tuesday 3:45-5:45, 15th floor CDS

Prof Wheelock - Wednesday 2:30-4, 1506 in CDS

Joey - 3:30-5:30 Thursday, 15th floor CDS

Pratik - 4:00-6:00 on Fridays, 15th floor CDS

Clarifying "one expression per scope"

fn classify_grade(score: f64) -> char {
    if score > 90.0 {
        'A'
    } else if score > 80.0 {
        'B'
    } else if score > 70.0 {
        'C'
    } else {
        'D'
    }
}

fn main(){
    println!("{}",classify_grade(5.5));
}

A complete solution to Friday's exercise

fn calculate_final_price(sticker_price: f64, tax_rate: f64, has_membership: bool) -> f64 {
    // Handle edge cases
    if sticker_price < 0.0 { println!("Warning: Negative price detected!"); }
    
    if tax_rate < 0.0 || tax_rate > 1.0 { println!("Warning: Unusual tax rate: {:.2}", tax_rate); }
    
    let final_price = if has_membership {
        sticker_price * (1.0 + tax_rate) * 0.9 
    } else {
        sticker_price * (1.0 + tax_rate)
    };
    
    println!("Final Price is ${:.2}", final_price);
    
    final_price
}

A few ways to write the core of the function (without printing)

#![allow(unused)]
fn main() {
fn calculate_final_price(sticker_price: f64, tax_rate: f64, has_membership: bool) -> f64 {
    if has_membership {
        sticker_price * (1.0 + tax_rate) * 0.9 
    } else {
        sticker_price * (1.0 + tax_rate)
    }
}
}
#![allow(unused)]
fn main() {
fn calculate_final_price(sticker_price: f64, tax_rate: f64, has_membership: bool) -> f64 {
    let mut final_price = sticker_price * (1.0 + tax_rate)
    if has_membership { final_price *= 0.9; };
    final_price
}
}
#![allow(unused)]
fn main() {
fn calculate_final_price(sticker_price: f64, tax_rate: f64, has_membership: bool) -> f64 {
    let membership_discount = if has_membership { 0.9 } else { 1.0 };
    sticker_price * (1.0 + tax_rate) * membership_discount
}
}

Learning Objectives

  • Use while, for, loop, break, and continue to build flexible loops
  • Use break to return values from loops and continue to skip iterations
  • Use for loops with ranges (.. and ..=), array iteration, and enumerate
  • Create and manipulate fixed-size arrays with indexing, sorting, and length operations
  • Apply loop labeling to control nested loop behavior
  • Choose appropriate loop types based on use case requirements

for loops and ranges

Usage: loop over a range or collection

A range is (start..end), e.g. (1..5), where the index will vary as

$$ \textrm{start} \leq \textrm{index} < \textrm{end}. $$

Unless you use the notation (start..=end), in which case the index will vary as

$$ \textrm{start} \leq \textrm{index} \leq \textrm{end} $$

#![allow(unused)]
fn main() {
for i in (1..5) {
    println!("{}",i);
};
}

More ways to play with ranges

#![allow(unused)]
fn main() {
for i in (1..5).rev() { // reverse order
    println!("{}",i)
};
}
#![allow(unused)]
fn main() {
for i in (1..=5) { // inclusive range
    println!("{}",i);
};
}
#![allow(unused)]
fn main() {
println!("This is a test");
for i in (1..5).step_by(2) { // every other element 
    println!("{}",i);
};
}
#![allow(unused)]
fn main() {
println!("And now for the reverse");
for i in (1..5).step_by(2).rev() {
    println!("{}",i)
};
}

I suggest always trying out / printing what you're looping over during early development to make sure it's doing what you want it to do!

Arrays in Rust

  • Arrays in Rust are of fixed length (we'll learn about more flexible Vec later)
  • All elements of the same type (unlike tuples)
  • You cannot add or remove elements from an array (but you can change its value)

What will this return?

#![allow(unused)]
fn main() {
let mut arr = [1,7,2,5,2];
arr[1] = 13;
println!("{} {}",arr[0],arr[1]);
println!("{}",arr.len());
}

for loop over an array

#![allow(unused)]
fn main() {
let mut arr = [1,7,2,5,2];
for x in arr {
    println!("{}",x);
};
}

Quick tricks for making arrays

#![allow(unused)]
fn main() {
// create array of given length and fill it with a specific value
// note the semi-colon vs the comma!
let arr2 = [15;3];
for x in arr2 {
    print!("{} ",x);
}
println!();
}
#![allow(unused)]
fn main() {
// you can still infer or annotate types
let arr2 : [u8;3] = [15;3];
}

Common Array Operations

Arrays come with useful built-in methods:

#![allow(unused)]
fn main() {
let mut scores = [85, 92, 78, 96, 88];

// Get the length
println!("Number of scores: {}", scores.len());

// Sort the array (modifies the original array)
scores.sort();
println!("Sorted scores: {:?}", scores);

// Check if array contains a value
println!("Contains 92? {}", scores.contains(&92));  // Note the & here!
println!("Contains 100? {}", scores.contains(&100));
}

Note: {:?} is debug formatting - prints the entire array contents

Iterating with Indices

Sometimes you need both the position and the value:

#![allow(unused)]
fn main() {
let fruits = ["apple", "banana", "orange"];

// Method 1: Using enumerate()
for (index, &fruit) in fruits.iter().enumerate() { // note the & here!
    println!("fruits[{}] = {}", index, fruit);
}

// Method 2: Using index range
for i in 0..fruits.len() {
    println!("fruits[{}] = {}", i, fruits[i]);
}
}

When to use each:

  • Use enumerate() when you need both index and value
  • Stick to index range when you need to modify array elements

Lecture 8 Review Quiz

Take 2 minutes with a partner to review functions from last lecture:

  1. What's wrong with this function signature?

    #![allow(unused)]
    fn main() {
    fn calculate_area(width, height) -> f64 {
    }
  2. What's wrong with this function?

    #![allow(unused)]
    fn main() {
     fn mystery(x: i32) -> i32 {
         let result = x * 2;
         result + 1;
     }
    }
  3. How can you fix this so it compiles?

    #![allow(unused)]
    fn main() {
    let x = 4;
    let y = 4.5;
    let z = x + y;
    println!("{}",z);
    }

while loops

While loops continue as long as a condition remains true (very similar to python)

#![allow(unused)]
fn main() {
let mut number = 3;

while number != 0 {
    println!("{number}!");
    number -= 1;
}
println!("LIFTOFF!!!");
}

(What's a nice bit of style feedback here?)

Infinite loop

#![allow(unused)]
fn main() {
loop {
    // THIS WILL RUN OVER AND OVER FOREVER
}
}
  • Similar to while (True) in python
  • Need to use break to jump out of the loop!

A loop can return a value (break can act like return)

Just like the body of a function, a loop is an expression

To quit early, with or without a value:

  • use return in a function
  • use break in a loop
#![allow(unused)]
fn main() {
let mut counter = 0;
let final_count = loop {
    counter += 1;
    if counter > 100 {
        break counter*2; // Return twice the final counter value
    }
};
println!("{}",final_count);
}

You can also break out of for and while loops but can't get a value out of them. Can you guess why? (hint: because it's Rust, it's about safety...)

Using continue to jump to the next iteration

Think/pair/share - what is this going to print?

#![allow(unused)]
fn main() {
let mut x = 1;

let result = loop {  // you can capture a return value
    if x == 3 {
        x = x+1;
        continue;    // skip the rest of this loop body and start the next iteration
    }
    println!("X is {}", x);
    x = x + 1;
    if x==6 {
        break x*2;   // break with a return value
    }
};

println!("Result is {}", result);
}

Labeling loops to target with continue and break.

Labels let you use continue or break on any nested layer.

USE CAUTION - this can make your code hard to read and it's probably a red flag if you find yourself needing it often

fn main() {
    let mut count = 0;
    'counting_up: loop {
        println!("count = {count}");
        let mut remaining = 10;

        loop {
            println!("remaining = {remaining}");
            if remaining == 9 {
                break;
            }
            if count == 2 {
                break 'counting_up;
            }
            remaining -= 1;
        }
        count += 1;
    }
    println!("End count = {count}");
}

Loop Selection Guidelines

For Loops:

  • Iterating over ranges or collections
  • When you need an index of what loop you're on

While Loops:

  • Continue until some condition changes
  • Don't know at the start how many times to loop

Loop (Infinite):

  • Breaking on complex conditions (too much to include in while)
  • Breaking at different places under different conditions

Activity time