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

ⅰ亾dé卋堺 提交于 2021-02-07 08:11:38

问题


Background

I know that the borrow checker disallows more than one mutable borrows. For example, the code below is invalid:

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

However, if the first borrow is dropped due to out of scope, the second borrow is valid:

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

Because of non-lexical lifetimes (NLL), the first borrow doesn't even have to be out of scope — the borrow checker only requires it not to be used anymore. So, the code below is valid in Rust 2018:

fn main() {
    let mut x = 1;

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

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

The Problem

But I don't understand why the code below is invalid:

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 != ' ')
}

Compile error message:

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`

From the error message I thought that maybe NLL doesn't support this case yet. So, I dropped i early:

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)

But I got a more confusing error message:

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

Why does it say first borrow later used here even if i is already dropped and out of scope before?


An Alternative Approach

The code is compiled if I change the signature of next function into this:

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

But still, I want to understand why the original next function doesn't work.


回答1:


Let's decipher this type deduction magic here. impl Iterator is actually a concrete type: Chars that wrapped with TakeWhile, so you may rewrite your method like this (btw, an interesting task is to determine the &char's lifetime):

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

Now you may see that the output type lives as long as the input and vice versa. That lifetime, in fact, derived from the &str that is originally used. Therefore, you may conclude that the resulted type lives as long as the used string (i.e. to the end of the main) and even explicit drop(i) won't help you, because compiler knows that the Chars is borrowed till the end. For working nll you must (unfortunately?) help the compiler:

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 != ' ')
}



回答2:


The problem is that you're explicitly telling the borrow checker that i lives as long as char_iter in the following block by declaring both to have the same lifetime 'a.

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

This means that the compiler believes that &mut char_iter is still in use as long as char_iter is still in scope. That is, until the end of main().



来源:https://stackoverflow.com/questions/62817320/why-does-the-borrow-checker-disallow-a-second-mutable-borrow-even-if-the-first-o

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!