为什么 Rust 生命周期会破坏循环中的可变引用?

Why do Rust lifetimes break mutable references in loops?

提问人:Rogus 提问时间:11/23/2022 最后编辑:Chayim FriedmanRogus 更新时间:11/23/2022 访问量:221

问:

在尝试重构一个运行良好的 Rust 应用程序时,我试图将循环的内容分离到一个新函数中。但是,在这个新重构的函数中,我需要传递一个必须是可变的参数,并通过引用传递。突然间,绝对在内联中工作的代码仅仅因为可变引用传递而崩溃了。

我的问题是:有人可以解释为什么这不适用于如此“简单”的更改吗?(即重构原本未更改的代码的新函数)

我有一个关于这个问题的最小演示,以及下面的几个工作比较。这是该代码中的错误:

error[E0499]: cannot borrow `str_to_int` as mutable more than once at a time
  --> src/main.rs:30:22
   |
30 |         get_key(key, &mut str_to_int);
   |                      ^^^^^^^^^^^^^^^ `str_to_int` was mutably borrowed here in the previous iteration of the loop

示例代码:

use std::collections::BTreeMap;

fn get_int (
    key: u32,
    values: &mut BTreeMap<u32, u32>,
) -> &u32 {
    values.entry(key).or_insert_with(|| { 1 })
}

fn get_key<'a> (
    key: &'a str,
    values: &'a mut BTreeMap<&'a str, u32>,
) -> &'a u32 {
    values.entry(key).or_insert_with(|| { 1 })
}

fn main() {
    let mut int_to_int = BTreeMap::new();
    for key in vec![1,2] {
        get_int(key, &mut int_to_int);
    }

    let mut str_to_int_inline = BTreeMap::new();
    for key in vec!["a","b"] {
        str_to_int_inline.entry(key).or_insert_with(|| { 1 });
    }

    let mut str_to_int = BTreeMap::new();
    for key in vec!["a","b"] {
        get_key(key, &mut str_to_int);
    }
}

请注意,第一个循环 () 与第三个循环 () 相同,只是键的数据类型不同,因为键不是引用,因此不需要指定生存期。第二个循环 () 与第三个循环 () 相同,只是行为是内联的,而不是在单独的函数中。int_to_intstr_to_intstr_to_int_inlinestr_to_int

关于这个话题有很多相关的问题和博客,但它们似乎都更具体地关注这个问题的特定版本,我想知道更通用的解释(根据我目前的理解)。如果答案已经只是为了更好地理解其中一个链接,我很乐意将这个问题标记为重复。

相关问题:

我读到的东西也让我想到了 https://github.com/rust-lang/polonius 这似乎也使它在未来能够发挥作用 - 有什么想法吗?

重构 可变 借用检查器

评论

0赞 MeetTitan 11/23/2022
问题确实是生命周期。您返回的密钥一直有效,直到删除所有具有生命周期的密钥。你能把你和参数的生存期分开吗?你的生存期与你的地图和返回值相同,使你的返回值一直存在,直到被丢弃,这直到结束才会发生'akeyvaluekeyvec!["a","b"]main()
0赞 Chayim Friedman 11/23/2022
不要在这里责怪 Rust 借用检查器,也不要指望 Polonius。有些模式现在无法表达,但这种情况是对生命周期的错误注释。
0赞 chabapok 11/24/2022
您可以使用板条箱polonius-the-crab

答:

5赞 Chayim Friedman 11/23/2022 #1

您的问题很简单:您错误地指定了生存期。get_key()

正确(和工作)的版本是:

fn get_key<'a, 'b>(key: &'a str, values: &'b mut BTreeMap<&'a str, u32>) -> &'b u32 {
    values.entry(key).or_insert_with(|| 1)
}

也许你已经猜到了发生了什么。

由于您同时使用了密钥本身及其密钥,这意味着您需要借用密钥的生命周期 - 。这意味着两件事:'aHashMapHashMap'static

  1. 你需要一个 ,而你没有。&'static mut HashMap
  2. 你在循环的第一次迭代中借用了 for,然后在下一次迭代中再次借用它,而它仍然被借用(因为它是借用的)。此错误隐藏了第一个错误,并且是编译器发出的唯一错误。HashMap'static'static

通常,将生存期与可变引用一起使用两次几乎总是错误的(共享引用更宽容,因为它们的类型是协变的)。

评论

0赞 Rogus 11/23/2022
哦,哇,谢谢你如此快速简洁的解释!