Shadowing in a loop

I made some modifications to the guessing game from the Rust book and attempted to use binary search to give the player a hint about where the correct answer might be. In my first approach, I created three immutable variables—start, end, and hint. My intention was for the binary search function to return updated values for these three variables in each iteration, which I would then reassign to the original variables using shadowing.

However, I noticed that after the first iteration, the values stopped updating. I eventually resolved the issue by making the variables mutable when initializing them and updating them directly within the binary search function instead of reassigning them.

I want to understand why my initial version, which used immutable variables and shadowing, did not work as expected.

use std::{cmp::Ordering, io};
use rand::Rng;

fn bisect(num: &i32, start: i32, end: i32) -> (i32, i32, String){
    let mid  = (start + end) / 2;
    match mid.cmp(num){
        Ordering::Equal => return (start, mid, String::from(format!("Try Again: The correct answer is between {start} and {mid}"))),
        Ordering::Greater => return (start, mid, String::from(format!("Try Again: The correct answer is between {start} and {mid}"))),
        Ordering::Less => return (mid, end, String::from(format!("Try Again: The correct answer is between {mid} and {end}")))
    }
}

fn main() {
    // use this
    let (mut start, mut end, mut hint) = (1, 100, String::from("Enter a number between zero and hundred"));
    /*
     this won't work
     let (start, end, hint) = (1, 100, String::from("Enter a number between zero and hundred")); 
    */
    
    println!("Welcome to guess the number: {hint}");
    let secret_number: i32 = rand::rng().random_range(start..=end);
    loop{
        let mut guess = String::new();
        io::stdin()
        .read_line(&mut guess)
        .expect("Failed to readline");
        let guess: i32 = match guess.trim().parse(){
            Ok(num) => num,
            Err(_) => continue
        };
        // use this 
        (start, end, hint) = bisect(&secret_number, start, end);
        /* 
        this won't work
        let (start, end, hint) = bisect(&secret_number, start, end);
        */
        
        match guess.cmp(&secret_number){
            Ordering::Equal => {println!("{guess} is the correct answer"); break},
            Ordering::Greater => println!("{hint}"),
            Ordering::Less => println!("{hint}"),
        }
        if (end - start) <= 1{
            println!("Game Over! You couldn't take the hint");
            break;
        }
    }
    
  
}
Summary

This text will be hidden

The shadowing variables are dropped at the end of the iteration. So the next loop iteration will use the start and end values from outside of the loop again.

3 Likes

Someone already solved this, but i want to mention some points on your code.

  1. format macro already returns an owned String so you don't need to call String::from.
  2. return keyword inside bisect function is unnecessary as the match statement is the last in the function body, but I get that maybe you like to see the return keyword for clarity.
  3. You can collapse the first two arms of the match statement inside bisect using
Ordering::Greater | Ordering::Equal => (start, mid, format!("Try again: ...")),

as their body are the same. (But Ordering::Equal shouldn't really happen because in that case the user has guessed the right number).
Same thing in the match inside main function, where you can write

Ordering::Greater | Ordering::Less => println!("{hint}");

I'm not sure what that phrase even means or how that was supposed to ever work. Shadowing doesn't actually do anything to the varaibles, it just explains how to use the same name without mixing them.

It's like having Joe in your school and Joe in the shop… they are entirely unrelated, different people, they just happen to have the same name! You can easily distinguish them as long as school and shop are far enough from each other so there are no confusion, but if you give something to Joe in the shop it would be silly to expect that different, unrelated, Joe in your school would notice.

Same with variables: shadowing just explains how one may create different variables with the same names but they are unrelated (except if you decide to make them related, of course: Joe from your school can pass something to Joe from the shop, it's not a crime).

3 Likes

The easiest way to understand shadowing is to rename the variables to different names. The confusion should then disappear:

let (start_1, end_1, hint_1) = (1, 100, String::from("Enter a number between zero and hundred"));
// [...]
let (start_2, end_2, hint_2) = bisect(&secret_number, start_1, end_1);
// [...]
if (end_2 - start_2) <= 1