нельзя заимствовать как изменяемый, потому что он уже заимствован при реализации graph

#rust #borrow-checker

Вопрос:

Я пытаюсь реализовать структуру графика, имея в каждом узле ссылки на его соседей. В частности, я пытаюсь создать сетку, в которой каждый узел содержит ссылки до 4 соседей — например, «2D-связанный список».

Но я получаю ошибку при назначении ссылок. Этот минималистичный пример воспроизводит мою проблему:

 #[derive(Clone)]
struct Node<'a> {
    neighbor: Option<amp;'a Node<'a>>, // optional reference to another Node
}

fn main() {

    // a bunch of nodes:
    let mut nodes: Vec<Node> = vec![ Node{neighbor: None}; 100];

    // I want node 0 to have a reference to node 1
    nodes[0].neighbor = Some(amp;nodes[1]);

}
 

Возникает следующая ошибка:

 error[E0502]: cannot borrow `nodes` as mutable because it is also borrowed as immutable
  --> src/main.rs:12:5
   |
12 |     nodes[0].neighbor = Some(amp;nodes[1]);
   |     ^^^^^------------------------------
   |     |                         |
   |     |                         immutable borrow occurs here
   |     mutable borrow occurs here
   |     immutable borrow later used here

error: aborting due to previous error

For more information about this error, try `rustc --explain E0502`.
 

Я изо всех сил пытаюсь понять, как это должно быть сделано в Rust. Должен ли я вместо этого использовать указатели?

Комментарии:

1. почему бы вам не хранить индексы вместо ссылок на сами узлы?

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

3. вы определенно можете так поступить, просто индексы тоже просты. Возможно, вы захотите взглянуть на Rc это .

4. Возможно , GhostCell мог бы вам помочь, он создан именно для этой цели. Вот репозиторий github с некоторыми коллекциями, использующими его , и вот сообщение reddit о 1d связанном списке

5. … и если ни один из безопасных подходов не работает для вас (эргономика, производительность, что угодно), вы всегда можете отказаться от небезопасных блоков и необработанных указателей

Ответ №1:

Вы не можете реализовать такую структуру данных именно по той причине, по которой была создана проверка заимствований. Это говорит вам о том, что ваша структура данных не может быть безопасной.

Причина, по которой это небезопасно, довольно проста.

Предположим, вы создали график с двумя элементами, и первый из них ссылается на второй. Теперь вы хотите, чтобы второй вариант ссылался на первый. Чтобы изменить свой график, вам потребуется получить изменяемый доступ к структуре. Но если вы получите изменяемый доступ к нему, ничто не сможет помешать вам удалить второй узел из графика, сделав ссылку в первом узле недействительной.

Таким образом, проверка заимствования не позволит вам этого сделать.

Для вас лучше всего использовать индексы вместо ссылок. Это будет иметь дополнительное преимущество: когда вы захотите сериализовать/десериализовать свою структуру, у вас не будет никаких проблем с этим.

Комментарии:

1. Это можно сделать с помощью узлов, выделенных в куче, с помощью Rc/Weak

2. Спасибо, теперь я понимаю, почему это запрещено, и в отличие от того, как я изначально думал, это не недостаток в компиляторе

3. … однако в гипотетическом сценарии, когда узлы могут быть удалены, я должен задаться вопросом, какую пользу приносит реализация индекса. Это «безопасно» с точки зрения компилятора, конечно, но индексы становятся недействительными, как и ссылки.

4. Если вы обманете компилятор (например, используя unsafe) и заставите его разрешить вам использовать ссылки и случайно разыменовать недопустимую ссылку, у вас будет нарушение доступа к памяти. Это гораздо худшая проблема, и ее гораздо сложнее отлаживать, чем паниковать в случае использования неправильного индекса без проверки.