Сборка
Как водится, начнём со сборки списка.
С нашей новой системой это довольно просто.
Метод new всё ещё тривиален — он просто заполняет все поля значением None.
Кроме того, поскольку создание узла стало достаточно громоздким, вынесем его в отдельный метод.
impl<T> Node<T> {
fn new(elem: T) -> Rc<RefCell<Self>> {
Rc::new(RefCell::new(Node {
elem: elem,
prev: None,
next: None,
}))
}
}
impl<T> List<T> {
pub fn new() -> Self {
List { head: None, tail: None }
}
}
> cargo build
**МНОГО ПРЕДУПРЕЖДЕНИЙ О НЕИСПОЛЬЗУЕМЫХ ПЕРЕМЕННЫХ, НО КОД СОБИРАЕТСЯ**
Ура!
Теперь попытаемся написать вставку в начало списка. Нам предстоит много работы, поскольку двусвязные списки существенно сложнее. Там, где в односвязном списке можно обойтись простой однострочной функцией, в двусвязном придётся писать КОД.
В частности, теперь мы должны обрабатывать новые граничные условия, связанные с пустыми списками.
Большинство операций затрагивают либо указатель head, либо указатель tail.
Однако при переходе к пустому списку и обратно, нам придётся редактировать оба указателя.
Простой способ убедиться в корректности наших методов — сохранять следующий инвариант: на каждый узел должно быть ровно два указателя. На каждый узел в седине списка указывают предшествующий ему и следующий за ним, в то время как на узлы на концах указывают поля списка.
Хорошо, приступим:
pub fn push_front(&mut self, elem: T) {
// новому узлы нужны +2 ссылки, любому другому +0
let new_head = Node::new(elem);
match self.head.take() {
Some(old_head) => {
// не-пустой список, надо связать со старой головой
old_head.prev = Some(new_head.clone()); // +1 new_head
new_head.next = Some(old_head); // +1 old_head
self.head = Some(new_head); // +1 new_head, -1 old_head
// всего: +2 new_head, +0 old_head -- правильно!
}
None => {
// пустой список, надо установить значение tail
self.tail = Some(new_head.clone()); // +1 new_head
self.head = Some(new_head); // +1 new_head
// всего: +2 new_head -- правильно!
}
}
}
cargo build
error[E0609]: no field `prev` on type `std::rc::Rc<std::cell::RefCell<fourth::Node<T>>>`
--> src/fourth.rs:39:26
|
39 | old_head.prev = Some(new_head.clone()); // +1 new_head
| ^^^^ unknown field
error[E0609]: no field `next` on type `std::rc::Rc<std::cell::RefCell<fourth::Node<T>>>`
--> src/fourth.rs:40:26
|
40 | new_head.next = Some(old_head); // +1 old_head
| ^^^^ unknown field
Что ж. Ошибка компилятора. Хорошее начало. Хорошее начало.
Почему нам недоступны поля prev и next в наших узлах?
Раньше это работало, потому что у нас был Rc<Node>.
Похоже, проблемы возникают из-за RefCell.
Кажется, пора прочитать документацию.
Гуглим "rust refcell"
Работа с изменяемой памятью с динамической проверкой правил заимствования
См. документацию на модуль for more.
щёлкает по ссылке
Разделяемые изменяемые контейнеры
Значения типов
Cell<T>иRefCell<T>можно изменять через разделяемые ссылки (т. е., в целом, через тип&T), в то время как большинство типов Rust можно изменять только через уникальные ссылки (&mut T). Говорят, чтоCell<T>иRefCell<T>обеспечивают «внутреннюю изменяемость» (interior mutability), в то время как обычные типы Rust демонстрируют «унаследованную изменяемость» (inherited mutability).Типы-ячейки бывают двух видов:
Cell<T>иRefCell<T>.Cell<T>предоставляет методыgetиset, которые изменяют внутреннее значение за один вызов метода. Однако,Cell<T>совместим только с типами, реализующимиCopy. Для других типов необходимо использовать типRefCell<T>, получая блокировку на запись перед изменением.
RefCell<T>использует время жизни Rust, чтобы реализовать «динамическое заимствование» — процесс, посредством которого можно претендовать на временный, эксклюзивный изменяемый доступ к внутреннему значению. Заимствование дляRefCell<T>отслеживается «во время выполнения», в отличие от обычных ссылочных типов Rust, которые отслеживаются полностью статически, во время компиляции. Поскольку заимствованияRefCell<T>динамичны, возможна попытка заимствовать значение, которое уже заимствовано на изменение; когда это происходит, в потоке возникает паника.Когда следует выбирать внутреннюю изменчивость
Обычная для языка унаследованная изменчивость предполагает, что для изменения значения требуется эксклюзивный доступ. Именно благодаря ей Rust может эффективно рассуждать о псевдонимах (указателях, ссылающихся на одну и ту же область памяти), предотвращая ошибки уже на этапе компиляции. По этой причине наследуемая изменчивость является предпочтительной, а внутреннюю изменчивость следует считать чем-то вроде крайней меры. Поскольку типы-ячейки допускают мутации там, где они обычно запрещены, встречаются ситуации, где внутренняя изменчивость не только уместна, но даже необходима. Например:
- Внедрение корней унаследованной изменчивости в разделяемые типы.
- Детали реализации логически чистых методов.
- Изменяющие реализации
Clone.Внедрение корней унаследованной изменчивости в разделяемые типы
Разделяемые умные типы-указатели, включая
Rc<T>иArc<T>предоставляют контейнеры, которые могут быть клонированы и разделены между несколькими частями приложения. Поскольку может существовать несколько ссылок на хранящиеся в них значения, их можно заимствовать только посредством разделяемых, а не изменяемых ссылок. Без ячеек было бы вообще невозможно менять данные внутри разделяемых боксов!Общей практикой является помещение
RefCell<T>в разделяемый тип-указатель, чтобы обеспечить возможность изменения:use std::collections::HashMap; use std::cell::RefCell; use std::rc::Rc; fn main() { let shared_map: Rc<RefCell<_>> = Rc::new(RefCell::new(HashMap::new())); shared_map.borrow_mut().insert("africa", 92388); shared_map.borrow_mut().insert("kyoto", 11837); shared_map.borrow_mut().insert("piccadilly", 11826); shared_map.borrow_mut().insert("marbles", 38); }Обратите внимание, что в этом примере используется
Rc<T>, а неArc<T>.RefCell<T>можно использовать только в однопоточных сценариях. ИспользуйтеMutex<T>, если вам нужна разделяемая изменчивость в многопоточном окружении.
Да, дока по Rust всё ещё невероятно крута.
Больше всего в приведённом коде нас интересует вот эта строка:
shared_map.borrow_mut().insert("africa", 92388);
А конкретно — вызов borrow_mut.
Похоже, мы должны явно заимствовать RefCell.
Оператор . за нас этого не делает.
Странно.
Попробуем:
pub fn push_front(&mut self, elem: T) {
let new_head = Node::new(elem);
match self.head.take() {
Some(old_head) => {
old_head.borrow_mut().prev = Some(new_head.clone());
new_head.borrow_mut().next = Some(old_head);
self.head = Some(new_head);
}
None => {
self.tail = Some(new_head.clone());
self.head = Some(new_head);
}
}
}
> cargo build
warning: field is never used: `elem`
--> src/fourth.rs:12:5
|
12 | elem: T,
| ^^^^^^^
|
= note: #[warn(dead_code)] on by default
Ага, получилось! Снова победили доки.