Удаление узлов в односвязном списке (две реализации)

У меня есть две реализации функции удаления. Метод 1 не работает, поскольку компилятор сказал, что curr заимствовано.

В чем разница между while let Some(boxed_node) = curr и match curr?

Способ 1

fn delete_method_1(&mut self, val: i32) {
    let mut curr = &mut self.head;

    while let Some(boxed_node) = curr {
        if boxed_node.val == val {
            *curr = boxed_node.next.take(); // it does not work
        } else {
            curr = &mut boxed_node.next
        }
    }
}
Сообщение об ошибке метода 1
while let Some(boxed_node) = curr {
   |                        ---------- `*curr` is borrowed here
26 |             if boxed_node.val == val {
27 |                 *curr = boxed_node.next.take(); // it does not work
   |                 ^^^^^
   |                 |
   |                 `*curr` is assigned to here but it was already borrowed
   |                 borrow later used here

Способ 2

fn delete_method_2(&mut self, val: i32) {
    let mut curr = &mut self.head;
    loop {
        match curr {
            None => return,
            Some(boxed_node) if boxed_node.val == val => {
                *curr = boxed_node.next.take();
            },
            Some(boxed_node) => {
                curr = &mut boxed_node.next;
            }
        }
    }
}

Я ожидал, что метод 1 сработает.

Было бы неплохо увидеть остальную часть сообщения об ошибке

pm100 06.07.2024 16:14

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

kmdreko 06.07.2024 16:52

Пожалуйста, опубликуйте Минимально воспроизводимый пример, то есть фрагмент кода, который мы можем скопировать в наши редакторы (или на игровую площадку), и он скомпилируется.

Chayim Friedman 06.07.2024 21:29
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
3
72
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

На самом деле разницы между while let и match нет. Если бы вы написали второй метод точно так же, как первый, он выглядел бы так:

fn delete_method_2(&mut self, val: i32) {
    let mut curr = &mut self.head;

    loop {
        match curr {
            None => return,
            Some(boxed_node) => {
                if boxed_node.val == val {
                    *curr = boxed_node.next.take();
                } else {
                    curr = &mut boxed_node.next;
                }
            }
        }
    }
}

И вы получите ту же самую ошибку.

То, что происходит на самом деле, немного тонко. Прежде всего, эргономика матча автоматически отменяет для вас участников проверки матча. Что на самом деле происходит это:

fn delete_method_2(&mut self, val: i32) {
    let mut curr = &mut self.head;

    loop {
        //    v Rust inserts this dereference...
        match *curr {
            None => return,
            //   vvvvvvv and this borrow automatically
            Some(ref mut boxed_node) => {
                if boxed_node.val == val {
                    *curr = boxed_node.next.take();
                } else {
                    curr = &mut boxed_node.next;
                }

            }
        }
    }
}

Версия while let выглядит почти так же. Проблема здесь в том, что строка curr = &mut boxed_node.next заставляет boxed_node быть заимствованной на всю жизнь curr. С поддельными прижизненными аннотациями:

fn delete_method_2(&mut self, val: i32) {
    let mut curr = &'curr mut self.head;

    loop {
        match *curr {
            None => return,
            Some(ref 'curr mut boxed_node) => {
                if boxed_node.val == val {
                    *curr = boxed_node.next.take();
                } else {
                    //            we force the compiler to match this lifetime
                    //      vvvvv exactly
                    curr = &'curr mut boxed_node.next;
                }

            }
        }
    }
}

Итак, поскольку boxed_node необходимо заимствовать для 'curr, нам не разрешено назначать *curr в той же области действия. Средство проверки заимствований (пока) не может определить, что двум ветвям необходимо заимствовать boxed_node на разные сроки жизни.

Однако если вы разделите случаи на две отдельные ветви соответствия, компилятор сможет определить соответствующие времена жизни, которые нужно заимствовать boxed_node для каждого случая:

fn delete_method_2(&mut self, val: i32) {
    let mut curr = &'curr mut self.head;

    loop {
        match *curr {
            None => return,
            Some(ref '_ mut boxed_node) if boxed_node.val == val => {
                *curr = boxed_node.next.take();
            }
            Some(ref 'curr mut boxed_node) => {
                curr = &'curr mut boxed_node.next;
            }
        }
    }
}

В первом случае компилятору просто нужно заимствовать boxed_node на время вызова take, после чего curr больше не считается заимствованным и мы можем его переназначить.

Во втором случае компилятор может заимствовать boxed_node на всю жизнь 'curr.

Вероятно, в этом объяснении есть множество технических ошибок, но в целом, я думаю, именно это и происходит.

Другие вопросы по теме