具有生存期限制的递归函数内部循环中的借入检查器错误

时间:2018-07-24 18:16:51

标签: loops recursion rust lifetime borrow-checker

借款检查员为什么抱怨此代码?

fn foo<'a>(v: &mut Vec<&'a str>, buf: &'a mut String) {
    loop {
        foo(v, buf);
    }
}
error[E0499]: cannot borrow `*buf` as mutable more than once at a time
 --> src/main.rs:3:16
  |
3 |         foo(v, buf);
  |                ^^^ mutable borrow starts here in previous iteration of loop
4 |     }
5 | }
  | - mutable borrow ends here

如果我删除了生命周期限制,则代码可以正常编译。

fn foo(v: &mut Vec<&str>, buf: &mut String) {
    loop {
        foo(v, buf);
    }
}

这不是Mutable borrow in a loop的副本,因为我的情况下没有返回值。


我很确定我的最终目标是在安全的Rust中无法实现的,但是现在我想更好地了解借位检查器的工作原理,并且我不明白为什么在参数之间添加有效期限制会延长有效期。借用此代码。

1 个答案:

答案 0 :(得分:2)

具有显式生存期'a的版本将Vec的生存期与buf的生存期联系起来。当重新借入VecString时会造成麻烦。在循环中将参数传递给foo时会发生重新借入:

fn foo<'a>(v: &mut Vec<&'a str>, buf: &'a mut String) {
    loop {
        foo(&mut *v, &mut *buf);
    }
}

此操作由编译器隐式完成,以防止在循环中调用foo时消耗参数。如果参数实际上已移动,则在对foo进行第一次递归调用之后,将无法再使用它们(例如,连续调用foo)。

强制buf移动可解决以下错误:

fn foo<'a>(v: &mut Vec<&'a str>, buf: &'a mut String) {
    foo_recursive(v, buf);
}

fn foo_recursive<'a>(v: &mut Vec<&'a str>, buf: &'a mut String) -> &'a mut String{
    let mut buf_temp = buf;
    loop {
        let buf_loop = buf_temp;
        buf_temp = foo_recursive(v, buf_loop);
        // some break condition
    }
    buf_temp
}

但是,一旦您尝试实际使用buf,事情就会再次崩溃。这是您的示例的摘要版本,说明了为什么编译器禁止连续buf的可变借用:

fn foo<'a>(v: &mut Vec<&'a str>, buf: &'a mut String) {
    bar(v, buf);
    bar(v, buf);
}

fn bar<'a>(v: &mut Vec<&'a str>, buf: &'a mut String) {
    if v.is_empty() {
        // first call: push slice referencing "A" into 'v'
        v.push(&buf[0..1]);
    } else {
        // second call: remove "A" while 'v' is still holding a reference to it - not allowed
        buf.clear();
    }
}

fn main() {
    foo(&mut vec![], &mut String::from("A"));
}

在您的示例中,对bar的调用等同于对foo的递归调用。再次,编译器抱怨*buf一次不能多次作为可变借借。提供的bar实现表明,bar上的生存期规范将允许该功能以v进入无效状态的方式实现。编译器仅通过查看bar的签名就可以理解,来自buf的数据可能会流入v,并且不管bar的实际实现如何,都将代码拒绝为潜在的不安全代码