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

3
在尝试重构一个本来运行良好的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_int`)与第三个循环(`str_to_int`)除了键的数据类型外完全相同--即键不是引用,因此不需要指定生命周期。第二个循环(`str_to_int_inline`)与第三个循环(`str_to_int`)的行为相同,但是行为是内联的而不是在一个单独的函数中。
有许多相关的问题和博客涉及此主题,但它们似乎更具体地关注特定版本的问题,我想知道更一般的解释(根据我的当前理解)。如果答案已经在这些链接中了,我将很乐意标记此问题为重复。
相关问题:

我读到的一些内容还提到了https://github.com/rust-lang/polonius,似乎也可以在未来使其工作--你有什么想法吗?


问题确实在于生命周期。你返回的关键字有效,直到所有具有生命周期 'a 的内容都被删除。你能分离出 keyvalue 参数的生命周期吗?你的 key 的生命周期与你的映射和返回值相同,这会使得你的返回值一直存在,直到 vec!["a", "b"] 被删除,而这只会发生在 main() 结束时。 - MeetTitan
不要在这里责怪Rust借用检查器,也不要指望Polonius。现在有一些无法表达的模式,但这种情况是生命周期注释错误的结果。 - Chayim Friedman
你可以使用 polonius-the-crab crate。 - chabapok
1个回答

5
你的问题非常简单:你错误地指定了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)
}

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

因为你在HashMap本身和它的键中都使用了'a,这意味着你需要借用HashMap的时间与键的生命周期-'static一样长。这意味着两件事:

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

通常,在可变引用中两次使用相同的生命周期几乎总是错误的(共享引用更容忍,因为它们在其类型上是协变的)。


哇,谢谢你提供如此迅速简洁的解释! - Rogus

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