为什么借用检查器不允许第二个可变借用,即使第一个已经超出范围?

Why does the borrow checker disallow a second mutable borrow even if the first one is already out of scope?

背景

我知道借用检查器不允许超过一个可变借用。例如,下面的代码是无效的:

fn main() {
    let mut x = 42;
    let a = &mut x;
    let b = &mut x;
    println!("{} {}", a, b);
}

但是,如果第一次借用因超出范围而被删除,则第二次借用有效:

fn main() {
    let mut x = 1;
    {
        let a = &mut x;
        println!("{}", a);
    }
    let b = &mut x;
    println!("{}", b);
}

因为 non-lexical lifetimes (NLL),第一次借用甚至不必超出范围——借用检查器只要求它不再被使用。因此,以下代码在 Rust 2018 中有效:

fn main() {
    let mut x = 1;

    let a = &mut x;
    println!("{}", a);

    let b = &mut x;
    println!("{}", b);
}

问题

但我不明白为什么下面的代码无效:

use std::str::Chars;

fn main() {
    let s = "ab".to_owned();
    let mut char_iter = s.chars();

    let mut i = next(&mut char_iter);
    dbg!(i.next());

    let mut j = next(&mut char_iter);
    dbg!(j.next());
}

fn next<'a>(char_iter: &'a mut Chars<'a>) -> impl Iterator<Item = char> + 'a {
    char_iter.take_while(|&ch| ch != ' ')
}

编译错误信息:

error[E0499]: cannot borrow `char_iter` as mutable more than once at a time
  --> src/main.rs:10:22
   |
7  |     let mut i = next(&mut char_iter);
   |                      -------------- first mutable borrow occurs here
...
10 |     let mut j = next(&mut char_iter);
   |                      ^^^^^^^^^^^^^^ second mutable borrow occurs here
11 |     dbg!(j.next());
12 | }
   | - first borrow might be used here, when `i` is dropped and runs the destructor for type `impl std::iter::Iterator`

从错误消息中我认为可能NLL 还不支持这种情况。所以,我早早放弃了i

use std::str::Chars;

fn main() {
    let s = "ab".to_owned();
    let mut char_iter = s.chars();

    {
        let mut i = next(&mut char_iter);
        dbg!(i.next());
    }

    let mut j = next(&mut char_iter);
    dbg!(j.next());
}

fn next<'a>(char_iter: &'a mut Chars<'a>) -> impl Iterator<Item = char> + 'a {
    char_iter.take_while(|&ch| ch != ' ')
}

(Rust Playground)

但我收到了一条更令人困惑的错误消息:

error[E0499]: cannot borrow `char_iter` as mutable more than once at a time
  --> src/main.rs:12:22
   |
8  |         let mut i = next(&mut char_iter);
   |                          -------------- first mutable borrow occurs here
...
12 |     let mut j = next(&mut char_iter);
   |                      ^^^^^^^^^^^^^^
   |                      |
   |                      second mutable borrow occurs here
   |                      first borrow later used here

为什么说 first borrow later used here 即使 i 之前已经删除并超出范围?


另一种方法

如果我将 next 函数的签名更改为:

,代码将被编译
fn next(char_iter: impl Iterator<Item = char>) -> impl Iterator<Item = char> {
    char_iter.take_while(|&ch| ch != ' ')
}

但是,我还是想明白为什么原来的next功能不起作用。

让我们在这里破译这个类型推导的魔法。 impl Iterator 实际上是一个具体类型:用 TakeWhile 包裹的 Chars,所以你可以像这样重写你的方法(顺便说一句,一个有趣的任务是确定 &char 的生命周期):

fn next<'a>(
    char_iter: &'a mut Chars<'a>,
) -> TakeWhile<&'a mut Chars<'a>, impl FnMut(&char) -> bool> {
    char_iter.take_while(|&ch| ch != ' ')
}

现在您可能会发现输出类型与输入类型一样长,反之亦然。那个lifetime,其实是从原来使用的&str衍生出来的。因此,您可能会得出结论,结果类型与使用的字符串一样长(即到 main 的末尾),甚至显式 drop(i) 也无济于事,因为编译器知道 Chars 借到最后。为了工作 nll 你必须(不幸的是?)帮助编译器:

fn next<'a, 'b: 'a>(
    char_iter: &'a mut Chars<'b>,
) -> TakeWhile<&'a mut Chars<'b>, impl FnMut(&char) -> bool> {
    char_iter.take_while(|&ch| ch != ' ')
}

问题在于,您通过声明两者具有相同的生命周期 'a 来明确告诉借用检查器 i 在下一个块中的生命与 char_iter 一样长。

fn next<'a>(char_iter: &'a mut Chars<'a>) -> impl Iterator<Item = char> + 'a {
    char_iter.take_while(|&ch| ch != ' ')
}

这意味着只要 char_iter 仍在范围内,编译器就认为 &mut char_iter 仍在使用中。即,直到main().

结束