Как время жизни в Rust влияет на изменчивость? - PullRequest
5 голосов
/ 28 января 2020

Я проверяю свое понимание времени жизни в Rust, явно аннотируя сигнатуры функций, и я создал пример, который я не уверен, что понимаю.

В этом примере я моделирую концепцию совместного использования книги и переворачивая страницу в нем. Для этого я использую одну изменяемую ссылку, которую я передаю функции borrow_and_read, которая обновляет поле curr_page структуры Book. Моя Book struct и main функция выглядит следующим образом:

#[derive(Debug)]
pub struct Book<'a> {
    pub title: &'a str,
    pub curr_page: Option<i32>,
    pub page_count: i32,
}

fn borrow_and_read<'a>(a_book: &'a mut Book<'a>) {
    match a_book.curr_page {
        Some(page) => a_book.curr_page = Some(page + 1),
        None => a_book.curr_page = Some(0),
    };
}

fn main() {
    let mut the_book: Book = Book {
        title: "The Book",
        curr_page: None,
        page_count: 104,
    };

    let a_book: &mut Book = &mut the_book;

    borrow_and_read(a_book);
    borrow_and_read(a_book);

    observe_book(&*a_book);
}

pub fn observe_book<'a>(a_book: &'a Book<'a>) {
    println!("Observing: {:?}", a_book);
}

( Playground )

Для моей первой реализации функции borrow_and_read я позволю компилятор добавил аннотации и все скомпилировано:

fn borrow_and_read(a_book: &mut Book) {
    match a_book.curr_page {
        Some(page) => a_book.curr_page = Some(page + 1),
        None => a_book.curr_page = Some(0),
    };
}

Затем я попытался добавить одну аннотацию времени жизни, указав время жизни как для ссылки, так и для экземпляра самого Book:

fn borrow_and_read<'a>(a_book: &'a mut Book<'a>) {
    match a_book.curr_page {
        Some(page) => a_book.curr_page = Some(page + 1),
        None => a_book.curr_page = Some(0),
    };
}

Это привело к следующим ошибкам:

error[E0499]: cannot borrow `*a_book` as mutable more than once at a time
  --> src/main.rs:25:21
   |
24 |     borrow_and_read(a_book);
   |                     ------ first mutable borrow occurs here
25 |     borrow_and_read(a_book);
   |                     ^^^^^^
   |                     |
   |                     second mutable borrow occurs here
   |                     first borrow later used here

error[E0502]: cannot borrow `*a_book` as immutable because it is also borrowed as mutable
  --> src/main.rs:27:18
   |
24 |     borrow_and_read(a_book);
   |                     ------ mutable borrow occurs here
...
27 |     observe_book(&*a_book);
   |                  ^^^^^^^^
   |                  |
   |                  immutable borrow occurs here
   |                  mutable borrow later used here

Обдумав то, что я первоначально попробовал, я решил, что имеет смысл разделить времена жизни изменчивой ссылки на Book и экземпляр Book сам. Затем я придумал:

fn borrow_and_read<'a, 'b>(a_book: &'a mut Book<'b>) 
where 'b : 'a {
    match a_book.curr_page {
        Some(page) => a_book.curr_page = Some(page + 1),
        None => a_book.curr_page = Some(0),
    };
}

, который компилирует и выводит ожидаемые результаты *1031*.

Я не понимаю, почему мое первоначальное сообщение об ошибке было таковым a_book был заимствован более чем один раз. Я думал, что все будет в порядке, передавая одну изменяемую ссылку, так как каждое использование ссылки понимает, что ссылка изменяемая. Это предположение, кажется, подтверждается окончательной реализацией моей функции borrow_and_read, но я не совсем уверен, почему указание, что время жизни экземпляра Book истекает с изменяемой ссылкой с where 'b : 'a, решает мою проблему.

Я надеюсь получить solid понимание того, как использование одного и того же времени жизни для изменяемой ссылки и Book экземпляра приводит к ошибкам, которые я получил.

1 Ответ

3 голосов
/ 28 января 2020

Проблема с вашим оригиналом в том, что время жизни слишком ограничено. Поскольку заимствование у Book имеет ту же длину, что и заимствование по названию книги ("The Book"), изменяемый заем вынужден длиться столько же, сколько и сама книга, а это означает, что он никогда не может быть заимствован неизменно.

Давайте рассмотрим это. Будет проще изучить вашу фиксированную версию, а затем посмотреть, что делает оригинал, чтобы ограничить ее.

fn borrow_and_read<'a, 'b>(a_book: &'a mut Book<'b>) 
where 'b : 'a {
    match a_book.curr_page {
        Some(page) => a_book.curr_page = Some(page + 1),
        None => a_book.curr_page = Some(0),
    };
}

Эта функция имеет два параметра времени жизни: один для самой книги и один для изменяемого заимствования на книга. Мы также ограничиваем 'b: 'a, что означает, что любые займы со сроком действия 'a действительны не дольше, чем займы со сроком действия 'b. Это на самом деле избыточно , так как компилятор может это видеть в любом случае. Имея аргумент с типом &'a mut Book<'b>, 'a уже не может длиться дольше, чем 'b.

Теперь давайте посмотрим на main. Мы назовем время жизни самой книги 'book. Мы будем называть время жизни изменчивого заимствования книги 'mtb. Наконец, мы назовем неизменный заем (на observe_book) 'imb. Давайте посмотрим, как долго должна длиться каждая жизнь.

// Initialize `the_book`. 'book has to start before this.

// Mutably borrow `the_book`. 'mtb has to start here.
let a_book: &mut Book = &mut the_book;

// Use the mutable borrow. 'mtb has to still be valid.
borrow_and_read(a_book);
// Use the mutable borrow. 'mtb has to still be valid.
borrow_and_read(a_book);

// Deref the mutable borrow and reborrow immutably.
// 'imb has to start here, so 'mtb has to end here.
// 'imb is a reference to `the_book`, so 'book has to still be active.
observe_book(&*a_book);

// The variables are no longer needed, so any outstanding lifetimes can end here
// That means 'imb and 'book end here.

Итак, суть проблемы заключается в том, что при этой настройке 'mtb должен закончиться до 'book. Теперь давайте посмотрим на исходную версию функции.

fn borrow_and_read<'a>(a_book: &'a mut Book<'a>) {
    match a_book.curr_page {
        Some(page) => a_book.curr_page = Some(page + 1),
        None => a_book.curr_page = Some(0),
    };
}

Теперь у нас есть только один параметр времени жизни, который заставляет время жизни заголовка и время изменяемого заимствования быть одинаковыми. Это означает, что 'mtb и 'book должны быть одинаковыми. Но мы только что показали, что 'mtb должен закончиться до 'book! Таким образом, с этим противоречием компилятор выдает нам ошибку. Я не знаю технических деталей того, почему ошибка cannot borrow* a_book as mutable more than once at a time, но я представляю, что компилятор думает об «использовании» переменной так же, как мы говорим о временах жизни. Поскольку 'book должно длиться до тех пор, пока вызов observe_book и 'mtb не будет таким же, как 'book, он рассматривает использование 'book как использование изменяемого заимствования. Опять же, я не совсем уверен в этом. Возможно, стоит подать вопрос, чтобы посмотреть, можно ли улучшить сообщение.


Я действительно сделал ie чуть выше. Хотя Rust не выполняет неявное приведение типов, он выполняет приведение к жизни. Займы с более длительным сроком службы могут быть вынуждены заимствовать с более коротким сроком службы. В конечном счете, это не имеет большого значения, но об этом стоит знать.

Название книги, строковый литерал, имеет тип &'static str, где 'static - это особое время жизни, которое длится для вся продолжительность программы. Данные встраиваются в двоичный код самой программы. Когда мы инициализируем the_book, он может иметь тип Book<'static>, но он также может быть приведен к Book<'book> для некоторого более короткого времени жизни 'book. Когда мы берем изменчивый заем, мы вынуждены иметь 'book: 'mtb, но у нас все еще нет других ограничений.

Когда мы вызываем однопараметрическую версию borrow_and_read, 'book и 'mtb оба должны быть сокращены до более короткого общего срока службы. (в этом случае, поскольку 'book: 'mtb, 'mtb будет работать - и действительно, это будет самый длинный срок службы, который будет работать). В двухпараметрической версии принуждение не требуется. 'book и 'mtb можно использовать как есть.

Теперь, когда мы разыменовываем a_book и постоянно его заимствуем, изменяемые заимствования не могут быть активными. Это означает, что mtb и более короткое время жизни, что и 1067 *, и 'mtb были вынуждены закончиться. Но у a_book есть время жизни 'book, и мы его используем, поэтому 'book не может закончиться. Отсюда ошибка.

В двухпараметрической версии 'book не был приведен к сокращению срока службы, поэтому он может продолжаться.

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