函数返回不可变的引用,但借用检查器不这么认为

Rya*_*rew 1 rust borrow-checker

在这里,我将一些可变引用传递给一个函数以对它们执行一些操作。然后,我通过将它们转换为不可变引用来删除这些可变引用。然而,Rust 借用检查器似乎仍然认为它们是可变的。这是代码:

//! src/lib.rs
fn append_1_to_all(strings: Vec<&mut String>) -> Vec<&mut String> {
    strings.into_iter().map(|s| { s.push_str("1"); s }).collect()
}

fn get_shared_references(strings: Vec<&mut String>) -> Vec<&String> {
    strings.into_iter().map(|c| &(*c)).collect()
}

#[test]
fn test() {
    let strings = vec!["one".to_string(), "two".to_string(), "three".to_string()];
    let strings_appended = append_1_to_all(strings.iter_mut().collect());

    let strings_shared = get_shared_references(strings_appended);

    assert_ne!(strings[0], *strings_shared[0]);
}
Run Code Online (Sandbox Code Playgroud)

编译错误:

error[E0502]: cannot borrow `strings` as immutable because it is also borrowed as mutable
  --> src/lib.rs:16:16
   |
12 |     let strings_appended = append_1_to_all(strings.iter_mut().collect());
   |                                            ------- mutable borrow occurs here
...
16 |     assert_ne!(strings[0], *strings_shared[0]);
   |                ^^^^^^^      -------------- mutable borrow later used here
   |                |
   |                immutable borrow occurs here
Run Code Online (Sandbox Code Playgroud)

我该如何解决这个错误?我认为strings_shared不应该再与可变借用相关。

Kev*_*eid 7

问题不在于您的引用实际上是可变的,而在于它使可变引用保持活动状态。

如果您有一个引用、类型&'a ...or &'a mut ...,并且您使用它生成另一个引用,无论是重新借用&*some_reference还是字段访问&some_struct_reference.some_field或方法或其他任何东西,那么它总是被认为是从原始引用中借用的,需要原始引用只要引用住就派生出来的。

请注意,在您的函数声明中,

fn get_shared_references(strings: Vec<&mut String>) -> Vec<&String> {...}
Run Code Online (Sandbox Code Playgroud)

如果我们更改它以写出省略的生命周期,我们会得到:

fn get_shared_references<'a>(strings: Vec<&'a mut String>) -> Vec<&'a String> {...}
Run Code Online (Sandbox Code Playgroud)

为了使这个工作,我们必须为结果写一些其他的生命周期,&'b String而不是`&'a String。但是那一生会是什么,它是如何受到限制的?它的持续时间不能超过它所引用,但要长于可变引用。Rust 的生命周期系统无法表达这一点。差点错过的是:

fn get_shared_references<'m, 'i: 'm>(strings: Vec<&'m mut String>) -> Vec<&'i String> {...}
Run Code Online (Sandbox Code Playgroud)

但即使你可以用这种类型实现一个函数,它对调用者也没有用,因为'i除了“至少只要'm”之外,他们对生命周期一无所知。

我们可以想象一个系统,其中可变引用具有两个生命周期——让我们假设语法&'m mut 'i T,其中'm是引用可变的生命周期,以及'i(不超过)值的生命周期。在那种情况下,我们可能有

fn consumes_mut_ref_produces_ref<'m, 'i>(x: &'m mut 'i i32) -> &'i i32 {...}
Run Code Online (Sandbox Code Playgroud)

但是必须设计一套全新的生命周期规则才能使这项工作发挥作用,甚至可能无法始终如一地做到这一点。这当然不是今天的 Rust 支持的东西。


我的建议是,在没有关于您要执行的操作的更多详细信息的情况下,使此代码适用于拥有的值。拥有的值可以按照您尝试的方式传递——因为将它们作为函数参数会转移所有权,因此无需担心生命​​周期,而且更有可能说:“我的函数消耗了这个并产生了那个, " 没有强制关系。

fn append_1_to_all(mut strings: Vec<String>) -> Vec<String> {
    for s in strings.iter_mut() {
        s.push_str("1")
    }
    strings
}

fn get_shared_references(strings: &Vec<String>) -> Vec<&String> {
    // Or even better, just use strings.iter() instead of this function
    strings.iter().collect()
}

#[test]
fn test() {
    let strings = vec!["one".to_string(), "two".to_string(), "three".to_string()];

    // .clone() here is necessarily only because strings is used in the assert below
    let strings_appended = append_1_to_all(strings.clone());

    let strings_shared = get_shared_references(&strings_appended);

    assert_ne!(strings[0], *strings_shared[0]);
}
Run Code Online (Sandbox Code Playgroud)