Основы
Ладно, самая ужасная часть книги.
Вот почему мне потребовалось 7 лет, чтобы написать эту главу!
Настало время ещё раз написать множеству скучнейших функций, которые мы писали уже 5 раз, но теперь они стали ещё многословнее и длиннее, потому теперь у нас два указателя формата Option<NonNull<Node<T>>>!
impl<T> LinkedList<T> {
pub fn new() -> Self {
Self {
front: None,
back: None,
len: 0,
_boo: PhantomData,
}
}
}
PhantomData — тип без полей и его можно создать, просто написав его имя. пожимает плечами
pub fn push_front(&mut self, elem: T) {
// БЕЗОПАСНОСТЬ: это связный список, что вы делаете?
unsafe {
let new = NonNull::new_unchecked(Box::into_raw(Box::new(Node {
front: None,
back: None,
elem,
})));
if let Some(old) = self.front {
// Вставляем новый передний узел перед старым
(*old).front = Some(new);
(*new).back = Some(old);
} else {
// Если переднего узла нет, у нас пустой список и нам надо
// установить и значение заднего узла. Кроме того, мы добавили
// несколько проверок, на случай, если что-то напутали.
debug_assert!(self.back.is_none());
debug_assert!(self.front.is_none());
debug_assert!(self.len == 0);
self.back = Some(new);
}
self.front = Some(new);
self.len += 1;
}
}
error[E0614]: type `NonNull<Node<T>>` cannot be dereferenced
--> src\lib.rs:39:17
|
39 | (*old).front = Some(new);
| ^^^^^^
Ах, да, именно поэтому я ненавижу своих детей, напичканных указателями.
Нам нужно явно извлечь сырой указатель из NotNull с помощью as_ptr, потому что DerefMut определён через &mut, а мы не хотим вводить безопасные ссылки в наш небезопасный код!
(*old.as_ptr()).front = Some(new);
(*new.as_ptr()).back = Some(old);
Compiling linked-list v0.0.3
warning: field is never read: `elem`
--> src\lib.rs:16:5
|
16 | elem: T,
| ^^^^^^^
|
= note: `#[warn(dead_code)]` on by default
warning: `linked-list` (lib) generated 1 warning (1 duplicate)
warning: `linked-list` (lib test) generated 1 warning
Finished test [unoptimized + debuginfo] target(s) in 0.33s
Прекрасно, теперь pop (и len):
pub fn pop_front(&mut self) -> Option<T> {
unsafe {
// Будем что-то делать, только если у списка есть передний узел.
// Обратите внимание, что мы больше не должны беспокоиться
// о `take`, потому что копирование сырых указателей не приводит к
// вызову деструкторов, если мы что-то напутаем... правда? :)
// Праааавда? :)))
self.front.map(|node| {
// Возвращаем к жизни Box, так что можно извлечь и уничтожить его
// значение (Box магически делает это за нас).
let boxed_node = Box::from_raw(node.as_ptr());
let result = boxed_node.elem;
// Делаем следующий узел новым передним узлом.
self.front = boxed_node.back;
if let Some(new) = self.front {
// Убираем ссылку переднего узла на удалённый узел.
(*new.as_ptr()).front = None;
} else {
// Если передний узел равен null, значит, список пустой!
debug_assert!(self.len == 1);
self.back = None;
}
self.len -= 1;
result
// Здесь Box неявным образом освобождается, потому что больше нет T.
})
}
}
pub fn len(&self) -> usize {
self.len
}
Compiling linked-list v0.0.3
Finished dev [unoptimized + debuginfo] target(s) in 0.37s
Мне кажется, всё в порядке, пора писать тест!
#[cfg(test)]
mod test {
use super::LinkedList;
#[test]
fn test_basic_front() {
let mut list = LinkedList::new();
// Пытаемся сломать пустой список
assert_eq!(list.len(), 0);
assert_eq!(list.pop_front(), None);
assert_eq!(list.len(), 0);
// Пытаемся сломать список из одного элемента
list.push_front(10);
assert_eq!(list.len(), 1);
assert_eq!(list.pop_front(), Some(10));
assert_eq!(list.len(), 0);
assert_eq!(list.pop_front(), None);
assert_eq!(list.len(), 0);
// Всё перемешиваем
list.push_front(10);
assert_eq!(list.len(), 1);
list.push_front(20);
assert_eq!(list.len(), 2);
list.push_front(30);
assert_eq!(list.len(), 3);
assert_eq!(list.pop_front(), Some(30));
assert_eq!(list.len(), 2);
list.push_front(40);
assert_eq!(list.len(), 3);
assert_eq!(list.pop_front(), Some(40));
assert_eq!(list.len(), 2);
assert_eq!(list.pop_front(), Some(20));
assert_eq!(list.len(), 1);
assert_eq!(list.pop_front(), Some(10));
assert_eq!(list.len(), 0);
assert_eq!(list.pop_front(), None);
assert_eq!(list.len(), 0);
assert_eq!(list.pop_front(), None);
assert_eq!(list.len(), 0);
}
}
Compiling linked-list v0.0.3
Finished test [unoptimized + debuginfo] target(s) in 0.40s
Running unittests src\lib.rs
running 1 test
test test::test_basic_front ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Ура, у нас всё идеально!
...Правда?