Невозможно получить изменяемую ссылку при итерации рекурсивной структуры: нельзя заимствовать как изменяемый более одного раза за раз

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

type Link = Option<Box<Node>>;

struct Node {
    next: Link
}

struct Recursive {
    root: Link
}

impl Recursive {
    fn back(&mut self) -> &mut Link {
        let mut anchor = &mut self.root;
        while let Some(ref mut node) = *anchor {
            anchor = &mut node.next;
        }
        anchor
    }
}

(Ржавчина детская площадка ссылка)

Тем не менее, это не удается:

error[E0499]: cannot borrow `anchor.0` as mutable more than once at a time
  --> src/main.rs:14:24
   |
14 |         while let Some(ref mut node) = *anchor {
   |                        ^^^^^^^^^^^^
   |                        |
   |                        second mutable borrow occurs here
   |                        first mutable borrow occurs here
...
18 |     }
   |     - first borrow ends here

error[E0506]: cannot assign to `anchor` because it is borrowed
  --> src/main.rs:15:13
   |
14 |         while let Some(ref mut node) = *anchor {
   |                        ------------ borrow of `anchor` occurs here
15 |             anchor = &mut node.next;
   |             ^^^^^^^^^^^^^^^^^^^^^^^ assignment to borrowed `anchor` occurs here

error[E0499]: cannot borrow `*anchor` as mutable more than once at a time
  --> src/main.rs:17:9
   |
14 |         while let Some(ref mut node) = *anchor {
   |                        ------------ first mutable borrow occurs here
...
17 |         anchor
   |         ^^^^^^ second mutable borrow occurs here
18 |     }
   |     - first borrow ends here

Это имеет смысл, так как оба anchor а также node относятся к той же структуре, но на самом деле мне все равно anchor больше после разрушения его.

Как мог back() быть правильно реализованным с помощью Safe Rust?

4 ответа

Решение

Это возможно... но мне бы хотелось иметь более элегантное решение.

Хитрость не в том, чтобы позаимствовать у anchorи, следовательно, жонглировать между двумя аккумуляторами:

  • один, содержащий ссылку на текущий узел
  • другой назначается ссылка на следующий узел

Это приводит меня к:

impl Recursive {
    fn back(&mut self) -> &mut Link {
        let mut anchor = &mut self.root;

        loop {
            let tmp = anchor;
            if let Some(ref mut node) = *tmp {
                anchor = &mut node.next;
            } else {
                anchor = tmp;
                break;
            }
        }

        anchor
    }
}

Не совсем красиво, но это то, что может заиметь средство проверки заимствования, так что ¯ \ _ (ツ) _ / ¯.

@ker улучшил это, создав безымянный временный:

impl Recursive {
    fn back(&mut self) -> &mut Link {
        let mut anchor = &mut self.root;

        loop {
            match {anchor} {
                &mut Some(ref mut node) => anchor = &mut node.next,
                other => return other,
            }
        }
    }
}

Хитрость в том, что с помощью {anchor} перемещает содержание anchor в неназванный временный объект, в котором выполняется совпадение. Поэтому в match блок мы не заимствуем из anchor но из временного, оставляя нам свободу изменять anchor, См. Соответствующий пост в блоге Stuff, который выполняет функция идентификации (в Rust).

Исходный код работает как есть, если включены нелексические времена жизни:

#![feature(nll)]

type Link = Option<Box<Node>>;

struct Node {
    next: Link
}

struct Recursive {
    root: Link
}

impl Recursive {
    fn back(&mut self) -> &mut Link {
        let mut anchor = &mut self.root;
        while let Some(node) = anchor {
            anchor = &mut node.next;
        }
        anchor
    }
}

fn main() {}

Нелексические времена жизни увеличивают точность средства проверки заимствований компилятора, позволяя ему видеть, что изменяемый заем anchor больше не используется. Мы также можем упростить ключевые слова в if let из-за недавних языковых изменений.

Вы можете использовать рекурсию, чтобы удовлетворить заемщика. Недостатком этого является создание стекового фрейма для каждого элемента в вашем списке. Если ваш список длинный, это обязательно приведет к переполнению стека. LLVM оптимизирует Node::back метод в цикле (см. LLVM IR генерируется на детской площадке)

impl Node {
    fn back(&mut self) -> &mut Link {
        match self.next {
            Some(ref mut node) => node.back(),
            None => &mut self.next,
        }
    }
}

impl Recursive {
    fn back(&mut self) -> Option<&mut Link> {
        self.root.as_mut().map(|node| node.back())
    }
}

Оно работает:

fn back(&mut self) -> &mut Link {
    let mut anchor = &mut self.root;
    while anchor.is_some(){
        anchor = &mut {anchor}.as_mut().unwrap().next;
    }
    anchor
}
Другие вопросы по тегам