【问题标题】:How to access mutable iterables inside a nested loop如何在嵌套循环中访问可变迭代
【发布时间】:2022-01-10 15:40:07
【问题描述】:

我时不时地在循环中借用(或不借用)可变变量时遇到同样的问题,最后我坐下来编译了一个最小的示例。结果,代码有点傻,但它是我能想到的最短的版本,突出了问题:

struct Association {
    used: bool,
    key: usize,
    value: String,
}

impl Association {
    fn new(key: usize, value: &str) -> Self {
        Association{used: false, key: key, value: value.to_string()}
    }
}

fn find_unused<'a>(data: &'a mut Vec<Association>) -> Option<&'a String> {
    for k in 0.. {
        for a in data {
            if a.key == k && !a.used {
                a.used = true;
                return Some(&a.value);
            }
        }
    }
    None
}

fn main() {
    let mut assoc = vec![
        Association::new(7, "Hello"),
        Association::new(9, "World")
    ];

    println!("{}", find_unused(&mut assoc).unwrap());
    println!("{}", find_unused(&mut assoc).unwrap());
}

这将失败并出现错误,因为 data 之前已移动。如果我改为借它,它会失败,因为它是以前借的。我想准确了解正在发生的事情以及如何解决它。特别是,我不想改变代码的结构,即使它很傻。我不想实现一个解决方法,因为这只是一个最小的例子:请假设循环的嵌套是“正确”的方法,即使它在这里完全愚蠢,它肯定是。

我只想知道如何与借阅检查器沟通这里发生的事情实际上是可以的。我知道一种方法可以做到这一点:

fn find_unused<'a>(data: &'a mut Vec<Association>) -> Option<&'a String> {
    for k in 0.. {
        for j in 0..data.len() {
            if data[j].key == k && !data[j].used {
                data[j].used = true;
                return Some(&data[j].value);
            }
        }
    }
    None
}

这编译没有错误并按预期工作。以我的幼稚理解,应该有一种方法可以用迭代器而不是索引来表达上述内容,我想知道如何做到这一点。

【问题讨论】:

  • 更好的代码play.integer32.com/…
  • 亲爱的@Stargateur,请注意我的免责声明并假设代码重组不是解决方案,即使它绝对适用于minimal示例。了解这实际上不是我正在编写的代码,并且以这种方式重组并不总是可行的。我确实在夜间频道上启用了非词汇生命周期支持,但至少我不能让它在启用该功能的情况下工作。
  • 您已经在使用 NLL,请阅读两次 shepmaster 的答案,这是当前 NLL 实现的当前限制。试试-Zpolonius。在我给出的解决方案上,它只是为了帮助您有一种使用迭代器的方法,如果您没有创建一个真正展示您的用例的minimal reproducible example,您就不能抱怨。我不是魔术师。
  • @JeskoHüttenhain 我不确定是否可以发布答案,但我最近遇到了类似的情况并偶然发现:github.com/rust-lang/rust/issues/51526 与我的代码非常匹配。似乎有条件地从循环中返回借用是特别的问题,它不知道结束循环的借用。 (而且我猜索引版本是有效的,因为没有借用数据。)但是,最终我对 NLL 的了解还不足以肯定地说。

标签: rust iterator borrow-checker mutability


【解决方案1】:

你想要的行为是可能的,如果你:

保持其余代码相同并专注于这部分,但将 return 注释掉:

fn find_unused<'a>(data: &'a mut Vec<Association>) -> Option<&'a String> {
    for k in 0.. {
        for a in data { // Error - but rustc says what to do here
            if a.key == k && !a.used {
                a.used = true;
                // return Some(&a.value);
            }
        }
    }
    None
}

来自 rustc 的错误消息说明了要做什么以及为什么。通过调用for a in data 中的into_iter() 移动(用完)参数中的可变借用。

rustc 推荐一个可变的重借。这样我们就不会试图借用已经移动的东西。进行更改(并暂时保留 return 注释掉),现在进行以下类型检查:

fn find_unused<'a>(data: &'a mut Vec<Association>) -> Option<&'a String> {
    for k in 0.. {
        for a in &mut *data { // we took rustc's suggestion
            if a.key == k && !a.used {
                a.used = true;
                // return Some(&a.value);
            }
        }
    }
    None
}

如果我们取消注释 return,我们会收到一条错误消息,指出我们返回的值的生命周期与 'a 不匹配。

但如果我们使用cargo +nightly rustc -- -Z polonius 运行,代码类型检查。

@stargateur 建议在comment 中尝试polonius

以下是我对 Polonius 为何提供帮助的猜测:

  • 对于当前的 rustc 借用检查器,函数签名中的生命周期在函数体内以相对粗略和简单的方式处理。我认为这里发生的情况是 'a 应该跨越整个函数体,但 a 的新可变借用仅跨越函数体的一部分,因此 Rust 没有统一它们。
  • Polonius 跟踪类型中借用的来源。所以它知道&amp;a.value的生命周期来自a,它来自data,它的生命周期是'a,所以Polonius知道return是可以的。

我是基于The Polonius Talk,但The Book 可能是最新的

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-10-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多