Реализация Курсоров

Мы займёмся типом CursorMut из стандартной библиотеки, поскольку неизменяемая версия на самом деле не так интересна. Как и в оригинальном дизайне, у неё есть «псевдоэлемент», который содержит None, чтобы обозначить начало/конец списка, и через который можно «пройти», чтобы перепрыгнуть на другую сторону списка. Для реализации нам потребуются:

  • Указатель на текущий узел
  • Указатель на список
  • Текущий индекс

Так, а каково должно быть значение индекса, когда мы указываем на «псевдоэлемент»?

хмурит брови... проверяет стандартную библиотеку... отвергает решение из стандартной библиотеки...

Ладно, вполне логично, что index Курсора возвращает Option<usize>. Стандартная реализация делает кучу ненужных вещей, чтобы избежать хранения индекса, как Option, но... у нас же связный список, это вполне нормально. Так же в стандартной библиотеке есть функции cursor_front/cursor_back, которые передвигают курсор на передний/задний элементы, что кажется интуитивным, пока речь не заходит о пустом списке.

Вы можете написать такой код, какой захотите, но я собираюсь выкинуть весь повторяющийся мусор, все граничные случае и сделать простой метод cursor_mut, который начинается в псевдоэлементе, и который можно передвигать вперёд/назад, чтобы получить нужную позицию. И, если, очень хотите, можете написать свой cursor_front.

Приступим:

pub struct CursorMut<'a, T> {
    cur: Link<T>,
    list: &'a mut LinkedList<T>,
    index: Option<usize>,
}

Всё довольно просто: одно поле на каждый пункт нашего списка! Теперь напишем метод cursor_mut:

impl<T> LinkedList<T> {
    pub fn cursor_mut(&mut self) -> CursorMut<T> {
        CursorMut { 
            list: self, 
            cur: None, 
            index: None,
        }
    }
}

Поскольку мы стартуем на псевдоэлементе, мы можем проинициализировать все поля значением None: красиво и и просто! Далее, перемещение:

impl<'a, T> CursorMut<'a, T> {
    pub fn index(&self) -> Option<usize> {
        self.index
    }

    pub fn move_next(&mut self) {
        if let Some(cur) = self.cur {
            unsafe {
                // Мы на реальном элементе, двигаемся к следующему (в сторону заднего)
                self.cur = (*cur.as_ptr()).back;
                if self.cur.is_some() {
                    *self.index.as_mut().unwrap() += 1;
                } else {
                    // Мы попали на псевдоэлемент, убираем индекс
                    self.index = None;
                }
            }
        } else if !self.list.is_empty() {
            // Мы на псевдоэлементе и у нас есть реальный передний элемент, так что перемещаемся на него!
            self.cur = self.list.front;
            self.index = Some(0)
        } else {
            // Мы на псевдоэлементе, но никаких других элементов нет... ничего не делаем.
        }
    }
}

Итак, есть 4 интересные варианта:

  • Обычный вариант
  • Обычный вариант, но мы перемещаемся к псевдоэлементу
  • Вариант псевдоэлемента, когда мы перемещаемся к переднему элементу
  • Вариант псевдоэлемента, но список пуст, так что ничего не делаем

В move_prev точно такая же логика, но мы меняем front/back местами и инвертируем изменение индекса:

pub fn move_prev(&mut self) {
    if let Some(cur) = self.cur {
        unsafe {
            // Мы на реальном элементе, двигаемся к предыдущему (в сторону переднего)
            self.cur = (*cur.as_ptr()).front;
            if self.cur.is_some() {
                *self.index.as_mut().unwrap() -= 1;
            } else {
                // Мы попали на псевдоэлемент, убираем индекс
                self.index = None;
            }
        }
    } else if !self.list.is_empty() {
        // Мы на псевдоэлементе и у нас есть реальный задний элемент, так что перемещаемся на него!
        self.cur = self.list.back;
        self.index = Some(self.list.len - 1)
    } else {
        // Мы на псевдоэлементе, но никаких других элементов нет... ничего не делаем.
    }
}

Добавим методы для просмотра элементов вокруг курсора: current, peek_next и peek_prev. Очень важное примечание: эти методы должны заимствовать наш курсор через &mut self и результаты должны быть привязаны к этому заимствованию. Мы не можем позволить пользователю получить несколько копий изменяемой ссылки и мы не можем позволить ему использовать любой из методов insert/remove/split/splice, пока у него есть такая ссылка!

К счастью, неявное выведение времени жизни работает в Rust именно так, как нужно, поэтому мы по умолчанию пишем правильный код!

pub fn current(&mut self) -> Option<&mut T> {
    unsafe {
        self.cur.map(|node| &mut (*node.as_ptr()).elem)
    }
}

pub fn peek_next(&mut self) -> Option<&mut T> {
    unsafe {
        self.cur
            .and_then(|node| (*node.as_ptr()).back)
            .map(|node| &mut (*node.as_ptr()).elem)
    }
}

pub fn peek_prev(&mut self) -> Option<&mut T> {
    unsafe {
        self.cur
            .and_then(|node| (*node.as_ptr()).front)
            .map(|node| &mut (*node.as_ptr()).elem)
    }
}

Даже думать не пришлось, всю работу сделали методы Option и (опущенные) ошибки компилятора. Я была настроена скептически по отношению к Option<NonNull>, но, будь я проклята, этот тип позволил мне писать код на автопилоте. Я потратила слишком много времени на написание коллекций на основе массивов, где вам никогда не приходится использовать Option, так что мне есть, с чем сравнивать! ((*node.as_ptr()), конечно, выглядит кринжово, но, с другой стороны, для вас это всего лишь обычные указатели Rust...)

Далее у нас есть выбор: мы можем сразу заняться split и splice, центральной темой этого API, или мы можем сделать небольшой шажок, реализовав insert/remove для одного элемента. У меня такое чувство, что нам придётся реализовать insert/remove через split и splice, так что... давайте попробуем и посмотрим, как ляжет карта (честно говоря, понятия не имею, пока пишу эти строки).

Разбиение

Итак, split_before и split_after возвращают всё до/после текущего элемента в виде LinkedList (и останавливаются на псевдоэлементе, а если они сразу находятся на псевдоэлементе, то возвращают весь список, а курсор после этого указывает на пустой список).

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

Я вижу 4 потенциально интересных варианта для split_before:

  • Обычный вариант
  • Обычный вариант, но предыдущий элемент является псевдоэлементом
  • Вариант псевдоэлемента, где мы возвращаем весь список, а оставляем пустой
  • Вариант псевдоэлемента, но список пустой, так что ничего не делаем и возвращаем пустой список

Начнём с граничных вариантов. Третий вариант, мне кажется, самый простой.

#![allow(unused)]
fn main() {
mem::replace(self.list, LinkedList::new())
}

Так? Список становится пустым, мы возвращаем весь предыдущий список и теперь наши поля хранят None, так что нам нечего исправлять. Прекрасно. О, кстати, этот метод закрывает и четвёртый вариант!

Так, теперь обычные варианты... ладно, здесь мне потребуется несколько ASCII-диаграмм. В самом общем случае, у нас есть что-то такое:

list.front -> A <-> B <-> C <-> D <- list.back
                          ^
                         cur

А мы хотим получить вот это:

list.front -> C <-> D <- list.back
              ^
             cur

return.front -> A <-> B <- return.back

Так что нам надо разорвать связь между cur и prev, и... боже, сколько всего надо менять. Ладно, мне просто надо разбить весь путь на отдельные шаги, чтобы быть уверенной, что я нигде не ошиблась. Будет чуток многословно, но я по крайней мере ничего не пропущу:

pub fn split_before(&mut self) -> LinkedList<T> {
    if let Some(cur) = self.cur {
        // Указываем на реальный элемент, так что список не пустой.
        unsafe {
            // Текущее состояние
            let old_len = self.list.len;
            let old_idx = self.index.unwrap();
            let prev = (*cur.as_ptr()).front;
            
            // Во что должен превратиться self
            let new_len = old_len - old_idx;
            let new_front = self.cur;
            let new_back = self.list.back;
            let new_idx = Some(0);

            // Что мы должны вернуть
            let output_len = old_len - new_len;
            let output_front = self.list.front;
            let output_back = prev;

            // Разрываем связь между cur и prev
            if let Some(prev) = prev {
                (*cur.as_ptr()).front = None;
                (*prev.as_ptr()).back = None;
            }

            // Создаём результат:
            self.list.len = new_len;
            self.list.front = new_front;
            self.list.back = new_back;
            self.index = new_idx;

            LinkedList {
                front: output_front,
                back: output_back,
                len: output_len,
                _boo: PhantomData,
            }
        }
    } else {
        // Мы на псевдолементе, просто меняем наш список на пустой.
        // Никаких других изменений состояния не нужно.
        std::mem::replace(self.list, LinkedList::new())
    }
}

Обратите внимание, что конструкция if-let помогает справиться с «обычным вариантом, когда предыдущий элемент — это псевдоэлемент»:

if let Some(prev) = prev {
    (*cur.as_ptr()).front = None;
    (*prev.as_ptr()).back = None;
}

Если вы хотите, можете рассмотреть весь код, как единое целое и сделать такие оптимизации:

  • заменить два обращения к (*cur.as_ptr()).front на вызов (*cur.as_ptr()).front.take()
  • заметить, что new_back не выполняет никакой работы, поэтому переменную можно удалить

Насколько я могу судить, оставшийся код должен работать. Узнаем, когда напишем тесты. (копи-пастим метод split_after)

Я больше не хочу совершать ошибки, я хочу писать самый надёжный код, который могу. Вот как я на самом деле разрабатывала коллекции: для начала разбивала задачу на тривиальные шаги и варианты, пока они не становились понятными и не помещались у меня в голове. Затем писала тонну тестов, чтобы быть уверенной, что не напортачила.

Поскольку большая часть моей работы с коллекциями крайней небезопасна, я, в целом, не могла полагаться на то, что компилятор обнаружит ошибки, а miri в то время ещё небыло! Поэтому мне приходилось изучать проблему, пока у меня не начинала болеть голова и стараться изо всех сил, чтобы Никогда Никогда Никогда не совершать ошибок.

Не пишите на Rust небезопасный код! Безопасный Rust гораздо лучше!!!

Вставка

Остался последний босс, с которым нужно сразиться — splice_before и splice_after. И в них, похоже, будет больше всего граничных случаев. Эти функции принимают на вход один LinkedList и вставляют его содержимое в наш список. Наш список может быть пустым, их список может быть пустым, нужно что-то решать с псевдоэлементами... вздыхает Давайте просто действовать шаг за шагом на примере splice_before.

  • Если их список пустой, ничего не делаем
  • Если наш список пустой, тогда их список становится нашим списком
  • Если мы указываем на псевдоэлемент, их список добавляется сзади (изменяя list.back)
  • Если мы указываем на первый элемент (0), их список добавляется спереди (изменяя list.front)
  • В других случаях, мы очень много возимся с указателями.

Общий случай:

input.front -> 1 <-> 2 <- input.back

 list.front -> A <-> B <-> C <- list.back
                     ^
                    cur

Превращается в:

list.front -> A <-> 1 <-> 2 <-> B <-> C <- list.back

Так? Так. Будем писать... **ГЛУБОКО ВЗДЫХАЕТ И ПИШЕТ:

    pub fn splice_before(&mut self, mut input: LinkedList<T>) {
        unsafe {
            if input.is_empty() {
                // Пустой входной список, ничего не делаем
            } else if let Some(cur) = self.cur {
                if let Some(0) = self.index {
                    // Добавляем спереди, сравните с добавлением сзади
                    (*cur.as_ptr()).front = input.back.take();
                    (*input.back.unwrap().as_ptr()).back = Some(cur);
                    self.list.front = input.front.take();

                    // Индекс увеличивается на длину входного списка
                    *self.index.as_mut().unwrap() += input.len;
                    self.list.len += input.len;
                    input.len = 0;
                } else {
                    // Общий случай, никаких граничных случаев:
                    // просто правим приватные поля
                    let prev = (*cur.as_ptr()).front.unwrap();
                    let in_front = input.front.take().unwrap();
                    let in_back = input.back.take().unwrap();

                    (*prev.as_ptr()).back = Some(in_front);
                    (*in_front.as_ptr()).front = Some(prev);
                    (*cur.as_ptr()).front = Some(in_back);
                    (*in_back.as_ptr()).back = Some(cur);

                    // Индекс увеличивается на длину входного списка
                    *self.index.as_mut().unwrap() += input.len;
                    self.list.len += input.len;
                    input.len = 0;
                }
            } else if let Some(back) = self.list.back {
                // Мы на псевдоэлементе и список не пуст, добавляем сзади.
                // Мы можем вызывать для входных указателей либо `take`,
                // либо `mem::forget`. Take корректнее, если мы используем
                // собственный аллокатор или что-то, что тоже требует очистки!
                (*back.as_ptr()).back = input.front.take();
                (*input.front.unwrap().as_ptr()).front = Some(back);
                self.list.back = input.back.take();
                self.list.len += input.len;
                // Не обязательно, но вежливо
                input.len = 0;
            } else {
                // Наш список пуст, заменяем его на входной, остаёмся на псевдоэлементе
                *self.list = input;
            }
        }
    }

Этот код по настоящему ужасен и я ощущаю всю боль от выбора Option<NonNull>. Но многое можно исправить. Во-первых, эти две строки можно вынести в самый конец, поскольку они нужны всегда. (согласна, в некоторых сценариях они не обязательны, но ничего не ломают, а установка input.len вызвана, скорее, паранойей):

self.list.len += input.len;
input.len = 0;

Использование перемещённого значения: input

Ах, да, в случае «наш список пуст» мы перемещаем list. Заменим эту строку на вызов swap:

// Наш список пуст, заменяем его на входной, остаёмся на псевдоэлементе
std::mem::swap(self.list, &mut input);

В этом случае операторы, меняющие значения self.list.len и input.len, не делают ничего, так как значения уже корректны, но они ничего и не ломают (и, в принципе, мы могли бы сделать в этом месте ранний возврат из функции).

Вызов unwrap вызван тем, что я рассматривала варианты в неудобном порядке. От него можно избавиться, если мы правильно зададим вопрос в операторе if-let:

if let Some(0) = self.index {

} else {
    let prev = (*cur.as_ptr()).front.unwrap();
}

Корректировка индекса повторяется в обоих ветках, так что и её можно вынести наружу:

#![allow(unused)]
fn main() {
*self.index.as_mut().unwrap() += input.len;
}

Сложив всё воедино, получаем:

#![allow(unused)]
fn main() {
if input.is_empty() {
    // Пустой входной список, ничего не делаем
} else if let Some(cur) = self.cur {
    // Оба списка не пустые
    if let Some(prev) = (*cur.as_ptr()).front {
        // Общий случай, никаких граничных случаев:
        // просто правим приватные поля
        let in_front = input.front.take().unwrap();
        let in_back = input.back.take().unwrap();

        (*prev.as_ptr()).back = Some(in_front);
        (*in_front.as_ptr()).front = Some(prev);
        (*cur.as_ptr()).front = Some(in_back);
        (*in_back.as_ptr()).back = Some(cur);
    } else {
        // Добавляем спереди, сравните с добавлением сзади
        (*cur.as_ptr()).front = input.back.take();
        (*input.back.unwrap().as_ptr()).back = Some(cur);
        self.list.front = input.front.take();
    }
    // Индекс увеличивается на длину входного списка
    *self.index.as_mut().unwrap() += input.len;
} else if let Some(back) = self.list.back {
    // Мы на псевдоэлементе и список не пуст, добавляем сзади.
    // Мы можем вызывать для входных указателей либо `take`,
    // либо `mem::forget`. Take корректнее, если мы используем
    // собственный аллокатор или что-то, что тоже требует очистки!
    (*back.as_ptr()).back = input.front.take();
    (*input.front.unwrap().as_ptr()).front = Some(back);
    self.list.back = input.back.take();

} else {
    // Наш список пуст, заменяем его на входной, остаёмся на псевдоэлементе
    std::mem::swap(self.list, &mut input);
}

self.list.len += input.len;
// Не обязательно, но вежливо
input.len = 0;

// input освобождается здесь
}

Ладно, это всё ещё отстой, но, в основном из-за... нет, так, я только что нашла ошибку:

#![allow(unused)]
fn main() {
    (*back.as_ptr()).back = input.front.take();
    (*input.front.unwrap().as_ptr()).front = Some(back);
}

Мы вызываем take у input.front и затем, буквально на следующей строке вызываем unwrap! вздыхает и то же самое мы делаем в эквивалентном зеркальном методе. Мы бы нашли эту ошибку с помощью тестов, но сейчас я пытаюсь быть Идеальной и пишу код в режиме реального времени, так что я увидела эту ошибку только что. Вот что бывает, когда выполняешь работу не так, как привыкла, и не в том порядке. Даёшь больше ясности в коде!

#![allow(unused)]
fn main() {
// Мы можем вызывать для входных указателей либо `take`,
// либо `mem::forget`. Take корректнее, если мы используем
// собственный аллокатор или что-то, что тоже требует очистки!
if input.is_empty() {
    // Пустой входной список, ничего не делаем
} else if let Some(cur) = self.cur {
    // Оба списка не пустые
    let in_front = input.front.take().unwrap();
    let in_back = input.back.take().unwrap();

    if let Some(prev) = (*cur.as_ptr()).front {
        // Общий случай, никаких граничных случаев:
        // просто правим приватные поля
        (*prev.as_ptr()).back = Some(in_front);
        (*in_front.as_ptr()).front = Some(prev);
        (*cur.as_ptr()).front = Some(in_back);
        (*in_back.as_ptr()).back = Some(cur);
    } else {
        // Нет предыдущего элемента, добавляем спереди
        (*cur.as_ptr()).front = Some(in_back);
        (*in_back.as_ptr()).back = Some(cur);
        self.list.front = Some(in_front);
    }
    // Индекс увеличивается на длину входного списка
    *self.index.as_mut().unwrap() += input.len;
} else if let Some(back) = self.list.back {
    // Мы на псевдоэлементе и список не пуст, добавляем сзади.
    let in_front = input.front.take().unwrap();
    let in_back = input.back.take().unwrap();

    (*back.as_ptr()).back = Some(in_front);
    (*in_front.as_ptr()).front = Some(back);
    self.list.back = Some(in_back);
} else {
    // Наш список пуст, заменяем его на входной, остаёмся на псевдоэлементе
    std::mem::swap(self.list, &mut input);
}

self.list.len += input.len;
// Не обязательно, но вежливо
input.len = 0;

// input освобождается здесь
}

Ладно, с таким кодом уже можно смириться. Единственный его недостаток в том, что мы два раза инициализируем in_front/in_back (возможно мы могли бы поправить наши условия, но не суть). Подобный код мы бы написали и на C, но Option<NonNull> делает его многословным. С этим можно жить. По уму, надо сделать сырые указатели удобнее для решения таких задач. Но это выходит за рамки этой книги.

В любом случае, я абсолютно измучена, так что insert, remove и все остальные методы оставляю читателю в качестве упражнения.

Финальный код нашего курсора вместе с копи-пастой комбинаторных методов. Всё ли здесь правильно? Это я узнаю только после написания следующего раздела и тестирования этого монстра!

pub struct CursorMut<'a, T> {
    list: &'a mut LinkedList<T>,
    cur: Link<T>,
    index: Option<usize>,
}

impl<T> LinkedList<T> {
    pub fn cursor_mut(&mut self) -> CursorMut<T> {
        CursorMut { 
            list: self, 
            cur: None, 
            index: None,
        }
    }
}

impl<'a, T> CursorMut<'a, T> {
    pub fn index(&self) -> Option<usize> {
        self.index
    }

    pub fn move_next(&mut self) {
        if let Some(cur) = self.cur {
            unsafe {
                // Мы на реальном элементе, двигаемся к следующему (в сторону заднего)
                self.cur = (*cur.as_ptr()).back;
                if self.cur.is_some() {
                    *self.index.as_mut().unwrap() += 1;
                } else {
                    // Мы попали на псевдоэлемент, убираем индекс
                    self.index = None;
                }
            }
        } else if !self.list.is_empty() {
            // Мы на псевдоэлементе и у нас есть реальный передний элемент, так что перемещаемся на него!
            self.cur = self.list.front;
            self.index = Some(0)
        } else {
            // Мы на псевдоэлементе, но никаких других элементов нет... ничего не делаем.
        }
    }

    pub fn move_prev(&mut self) {
        if let Some(cur) = self.cur {
            unsafe {
                // Мы на реальном элементе, двигаемся к предыдущему (в сторону переднего)
                self.cur = (*cur.as_ptr()).front;
                if self.cur.is_some() {
                    *self.index.as_mut().unwrap() -= 1;
                } else {
                    // Мы попали на псевдоэлемент, убираем индекс
                    self.index = None;
                }
            }
        } else if !self.list.is_empty() {
            // Мы на псевдоэлементе и у нас есть реальный задний элемент, так что перемещаемся на него!
            self.cur = self.list.back;
            self.index = Some(self.list.len - 1)
        } else {
            // Мы на псевдоэлементе, но никаких других элементов нет... ничего не делаем.
        }
    }

    pub fn current(&mut self) -> Option<&mut T> {
        unsafe {
            self.cur.map(|node| &mut (*node.as_ptr()).elem)
        }
    }

    pub fn peek_next(&mut self) -> Option<&mut T> {
        unsafe {
            self.cur
                .and_then(|node| (*node.as_ptr()).back)
                .map(|node| &mut (*node.as_ptr()).elem)
        }
    }

    pub fn peek_prev(&mut self) -> Option<&mut T> {
        unsafe {
            self.cur
                .and_then(|node| (*node.as_ptr()).front)
                .map(|node| &mut (*node.as_ptr()).elem)
        }
    }

    pub fn split_before(&mut self) -> LinkedList<T> {
        // У нас есть что-то такое:
        //
        //     list.front -> A <-> B <-> C <-> D <- list.back
        //                               ^
        //                              cur
        //
        // А мы хотим получить вот это:
        //
        //     list.front -> C <-> D <- list.back
        //                   ^
        //                  cur
        //
        //     return.front -> A <-> B <- return.back
        //
        if let Some(cur) = self.cur {
            // Указываем на реальный элемент, так что список не пустой.
            unsafe {
                // Текущее состояние
                let old_len = self.list.len;
                let old_idx = self.index.unwrap();
                let prev = (*cur.as_ptr()).front;
                
                // Во что должен превратиться self
                let new_len = old_len - old_idx;
                let new_front = self.cur;
                let new_back = self.list.back;
                let new_idx = Some(0);

                // Что мы должны вернуть
                let output_len = old_len - new_len;
                let output_front = self.list.front;
                let output_back = prev;

                // Разрываем связь между cur и prev
                if let Some(prev) = prev {
                    (*cur.as_ptr()).front = None;
                    (*prev.as_ptr()).back = None;
                }

                // Создаём результат:
                self.list.len = new_len;
                self.list.front = new_front;
                self.list.back = new_back;
                self.index = new_idx;

                LinkedList {
                    front: output_front,
                    back: output_back,
                    len: output_len,
                    _boo: PhantomData,
                }
            }
        } else {
            // Мы на псевдолементе, просто меняем наш список на пустой.
            // Никаких других изменений состояния не нужно.
            std::mem::replace(self.list, LinkedList::new())
        }
    }

    pub fn split_after(&mut self) -> LinkedList<T> {
        // У нас есть что-то такое:
        //
        //     list.front -> A <-> B <-> C <-> D <- list.back
        //                         ^
        //                        cur
        // 
        //
        // А мы хотим получить вот это:
        // 
        //     list.front -> A <-> B <- list.back
        //                         ^
        //                        cur
        //
        // 
        //     return.front -> C <-> D <- return.back
        //
        if let Some(cur) = self.cur {
            // Указываем на реальный элемент, так что список не пустой.
            unsafe {
                // Текущее состояние
                let old_len = self.list.len;
                let old_idx = self.index.unwrap();
                let next = (*cur.as_ptr()).back;
                
                // Во что должен превратиться self
                let new_len = old_idx + 1;
                let new_back = self.cur;
                let new_front = self.list.front;
                let new_idx = Some(old_idx);

                // Что мы должны вернуть
                let output_len = old_len - new_len;
                let output_front = next;
                let output_back = self.list.back;

                // Разрываем связь между cur и next
                if let Some(next) = next {
                    (*cur.as_ptr()).back = None;
                    (*next.as_ptr()).front = None;
                }

                // Создаём результат:
                self.list.len = new_len;
                self.list.front = new_front;
                self.list.back = new_back;
                self.index = new_idx;

                LinkedList {
                    front: output_front,
                    back: output_back,
                    len: output_len,
                    _boo: PhantomData,
                }
            }
        } else {
            // Мы на псевдолементе, просто меняем наш список на пустой.
            // Никаких других изменений состояния не нужно.
            std::mem::replace(self.list, LinkedList::new())
        }
    }

    pub fn splice_before(&mut self, mut input: LinkedList<T>) {
        // Наше:
        //
        // input.front -> 1 <-> 2 <- input.back
        //
        //  list.front -> A <-> B <-> C <- list.back
        //                      ^
        //                     cur
        //
        // Превращается в:
        //
        // list.front -> A <-> 1 <-> 2 <-> B <-> C <- list.back
        //
        unsafe {
            // Мы можем вызывать для входных указателей либо `take`,
            // либо `mem::forget`. Take корректнее, если мы используем
            // собственный аллокатор или что-то, что тоже требует очистки!
            if input.is_empty() {
                // Пустой входной список, ничего не делаем
            } else if let Some(cur) = self.cur {
                // Оба списка не пустые
                let in_front = input.front.take().unwrap();
                let in_back = input.back.take().unwrap();

                if let Some(prev) = (*cur.as_ptr()).front {
                    // Общий случай, никаких граничных случаев:
                    // просто правим приватные поля
                    (*prev.as_ptr()).back = Some(in_front);
                    (*in_front.as_ptr()).front = Some(prev);
                    (*cur.as_ptr()).front = Some(in_back);
                    (*in_back.as_ptr()).back = Some(cur);
                } else {
                    // Нет предыдущего элемента, добавляем спереди
                    (*cur.as_ptr()).front = Some(in_back);
                    (*in_back.as_ptr()).back = Some(cur);
                    self.list.front = Some(in_front);
                }
                // Индекс увеличивается на длину входного списка
                *self.index.as_mut().unwrap() += input.len;
            } else if let Some(back) = self.list.back {
                // Мы на псевдоэлементе и список не пуст, добавляем сзади.
                let in_front = input.front.take().unwrap();
                let in_back = input.back.take().unwrap();

                (*back.as_ptr()).back = Some(in_front);
                (*in_front.as_ptr()).front = Some(back);
                self.list.back = Some(in_back);
            } else {
                // Наш список пуст, заменяем его на входной, остаёмся на псевдоэлементе
                std::mem::swap(self.list, &mut input);
            }

            self.list.len += input.len;
            // Не обязательно, но вежливо
            input.len = 0;
            
            // input освобождается здесь
        }        
    }

    pub fn splice_after(&mut self, mut input: LinkedList<T>) {
        // Наше:
        //
        // input.front -> 1 <-> 2 <- input.back
        //
        // list.front -> A <-> B <-> C <- list.back
        //                     ^
        //                    cur
        //
        //
        // Превращается в :
        //
        // list.front -> A <-> B <-> 1 <-> 2 <-> C <- list.back
        //                     ^
        //                    cur
        //
        unsafe {
            // Мы можем вызывать для входных указателей либо `take`,
            // либо `mem::forget`. Take корректнее, если мы используем
            // собственный аллокатор или что-то, что тоже требует очистки!
            if input.is_empty() {
                // Пустой входной список, ничего не делаем
            } else if let Some(cur) = self.cur {
                // Оба списка не пустые
                let in_front = input.front.take().unwrap();
                let in_back = input.back.take().unwrap();

                if let Some(next) = (*cur.as_ptr()).back {
                    // Общий случай, никаких граничных случаев:
                    // просто правим приватные поля
                    (*next.as_ptr()).front = Some(in_back);
                    (*in_back.as_ptr()).back = Some(next);
                    (*cur.as_ptr()).back = Some(in_front);
                    (*in_front.as_ptr()).front = Some(cur);
                } else {
                    // Нет следующего элемента, добавляем сзади
                    (*cur.as_ptr()).back = Some(in_front);
                    (*in_front.as_ptr()).front = Some(cur);
                    self.list.back = Some(in_back);
                }
                // Индекс не меняется
            } else if let Some(front) = self.list.front {
                // Мы на псевдоэлементе и список не пуст, добавляем спереди.
                let in_front = input.front.take().unwrap();
                let in_back = input.back.take().unwrap();

                (*front.as_ptr()).front = Some(in_back);
                (*in_back.as_ptr()).back = Some(front);
                self.list.front = Some(in_front);
            } else {
                // Наш список пуст, заменяем его на входной, остаёмся на псевдоэлементе
                std::mem::swap(self.list, &mut input);
            }

            self.list.len += input.len;
            // Не обязательно, но вежливо
            input.len = 0;
            
            // input освобождается здесь
        }        
    }
}