Почему средство проверки заимствований запрещает второе изменяемое заимствование, даже если первое уже выходит за рамки области действия? - PullRequest
4 голосов
/ 09 июля 2020

Фон

Я знаю, что программа проверки заимствований запрещает более одного изменяемого заимствования. Например, приведенный ниже код недействителен:

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);
}

Из-за нелексическое время жизни (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 функция не работает.

Ответы [ 2 ]

4 голосов
/ 09 июля 2020

Давайте расшифруем этот вид дедукции magi c здесь. impl Iterator на самом деле является конкретным типом: Chars, обернутым TakeWhile, поэтому вы можете переписать свой метод следующим образом (кстати, интересная задача - определить время жизни &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 != ' ')
}

Теперь вы можете видеть, что тип вывода живет столько же, сколько и ввод, и наоборот. Фактически, это время жизни происходит от &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 != ' ')
}
2 голосов
/ 09 июля 2020

Проблема в том, что вы явно указываете программе проверки заимствований, что i живет столько же, сколько char_iter в следующем блоке, объявляя, что оба имеют одинаковое время жизни 'a. * Это означает, что компилятор считает, что &mut char_iter все еще используется, пока char_iter все еще находится в области видимости. То есть до конца main().

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...