为什么 `while let Some(variable) = ...` 不会重新赋值 `variable`?

3

我试图用循环爬取路径,直到路径上满足某个条件为止。似乎使用while let 循环,并在每次循环中将循环变量重新赋值为其parent是一种有效的方式。

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;
    }
}

然而,这会导致无限循环,因为在循环语句中未重新分配path
我本来期望while let Some(path) = path.parent()语句会在每次迭代中重新分配path,但实际上并没有发生这种情况,path.parent()path没有改变。 即上面程序的输出将一直重复显示"/my/long/path/to/a",直到手动终止程序。
可以通过分离两个变量并在循环内手动重新分配它来解决这个问题。
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;
}

这是因为 path.parent() 的路径和 let Some(path) 的路径虽然名称相同,但它们被不同的作用域隔离开了。您是否能详细说明我的误解?有没有更常用的方法来完成这种操作?

1
是的,在 Some(path) 中的 path 遮蔽了外部的 path - PitaJ
顺便说一句,我可能会像这样做 path.ancestors().any(path_matches_condition),而不是使用你的 while 循环。 - PitaJ
@PitaJ,非常好的建议。在我的实际用例中,我需要匹配条件的第一个祖先,因此我不认为path.ancestors方法会很顺利地工作。我不确定ancestors的顺序是否得到保证,以及是否有一种好的方法在第一次匹配后跳出迭代。如果您有建议,我欢迎另一个答案。 - bicarlsen
1
在这种情况下,path.ancestors().find(path_matches_condition)将返回符合条件的第一个祖先。从该方法的文档中可以看出,它从path开始向后迭代:path.parent()path.parent().parent()等。 - PitaJ
1个回答

1
“你所做的和文档的显著区别在于,你确实从未重新分配path - 但我认为你误解了它没有被重新分配的位置。”
这句话的意思是:你的操作和文档之间的显著区别在于,你从未重新赋值变量“path”,但我认为你误解了它没有被重新赋值的位置。
let path = ...
while let Some(p) = path.parent() { ... }

将 `path` 作为参数,并调用 `parent()`。在 `Some(p)` 中的 `p` 不是与 `path` 相同的变量。在您的代码中,当您放置 `Some(path)` 时,您会遮蔽外部作用域的 `path` 变量。但是,一旦第一个循环结束,该影子就会消失,并且您实际上没有重新分配 `path` 的值,因此 `path.parent()` 返回与之前相同的值。
因此,您可能想要这样做:
let path = (...)
let parent_path = path.parent()
while let Some(p) = parent_path { 
    ... 
    parent_path = p.parent();
}

请注意,阴影效果的出现是因为 let Some(p) = path.parent() 进行了解构。就像匹配语句一样,请考虑以下代码:
fn main() {
    let path: Option<&str> = Some("A path");
    match path {
        Some(path: &str) => println!("{:#?}", path),
        None => println!("Nuthin!")
    }
    println!("{:#?}", path);
}

这段代码可以编译通过,但我们可以清楚地看到第一个 path 是一个 Option 类型,而内部的是一个 &str 类型。输出结果为:
"A path"
Some(
    "A path",
)

但是一旦循环结束,那个影子就消失了,这意味着你永远无法真正获得父级。什么?他的第二个例子清楚地表明,你所需要做的就是消除阴影效应(在Some内绑定到不同的名称),它就可以正常工作。 - PitaJ
@PitaJ澄清了这个句子。是的,所有OP需要做的就是消除阴影效应,尽管很明显OP不确定导致这种情况成立的机制。 - Nathaniel Ford

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接