Home > Net >  Reassigning loop variable in `while let` loop does not work
Reassigning loop variable in `while let` loop does not work

Time:09-21

I am trying to crawl up a path in a loop until a certain condition on the path is met. It seemed like an efficient way to do this would be to use a while let loop reassigning the loop variable to its parent on each loop.

let path = std::path::Path::new("/my/long/path/to/a/file.txt");
while let Some(path) = path.parent() {
    println!("{:?}", path);
    
    if path_matches_condition(&path) {
        println!("{:?} path matched", path);
        break;
    }
}

However, this results in an infinite loop because path is not reassigned in the loop statement.

I would have expected the while let Some(path) = path.parent() statement to reassign path each time, but this does not occur, and the path of path.parent() does not change. i.e. The output of the program above would be "/my/long/path/to/a" repeated until the program is manually terminated.

This can be remedied by separating the two variables and manually reassigning it inside the loop.

let mut path = std::path::Path::new("/my/long/path/to/a/file.txt");
while let Some(parent) = path.parent() {
    println!("{:?}", path);
    
    if path_matches_condition(&path) {
        println!("{:?} path matched", path);
        break;
    }

    path = parent;
}

Is this happening because, although the path of path.parent() and the path of let Some(path) have the same name, they are separated by scope? Could you elaborate on my misunderstanding? Is there a more idiomatic way to do this sort of thing?

CodePudding user response:

The salient difference between what you did and what the documentation has is that you are, indeed, never re-assigning path - but I think you misunderstand where it doesn't get reassigned.

This statement:

let path = ...
while let Some(p) = path.parent() { ... }

Takes path and calls parent(). The p in Some(p) is not the same variable as path. In your code, when you put Some(path) you're shadowing the outer scoped path variable. But that shadow goes away once the first loop ends, and you've not actually re-assigned the path value, so path.parent() returns the same value as before.

Therefore, you probably want this:

let path = (...)
let parent_path = path.parent()
while let Some(p) = parent_path { 
    ... 
    parent_path = p.parent();
}

Note the shadowing occurs because the let Some(p) = path.parent() is doing a destructuring. Much like with a match statement - consider this code:

fn main() {
    let path: Option<&str> = Some("A path");
    match path {
        Some(path: &str) => println!("{:#?}", path),
        None => println!("Nuthin!")
    }
    println!("{:#?}", path);
}

This compiles cleanly, but we can clearly see that the first path is an Option, while the inner one is a &str. The output is:

"A path"
Some(
    "A path",
)
  • Related