Как сгладить древовидную структуру в Rust до Vec<&mut...>?

У меня есть древовидная структура в Rust:

#[derive(Debug, Clone)]
struct Dir {
    name: String,
    children: Vec<Node>,
}

#[derive(Debug, Clone)]
enum Node {
    File(File),
    Dir(Dir),
}

#[derive(Debug, Clone)]
struct FileSystem {
    root: Dir,
}

Я хотел сгладить эту структуру в Vec<&mut Node> (порядок не имеет значения).

Моя идея состояла в том, чтобы рекурсивно пройтись по дереву и добавить все узлы в vec:

impl Node {
    fn flatten(&mut self, nodes: &mut Vec<&mut Node>) {
        nodes.push(self);

        if let Node::Dir(dir) = self {
            for node in dir.children.iter_mut() {
                node.flatten(nodes);
            }
        }
    }
}

Очевидно, я получаю сообщение об ошибке, потому что self уже перемещено в nodes, но я понятия не имею, как решить эту проблему. Я пробовал использовать Rc и RefCell, но мало о них знаю.

Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
1
0
98
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

Это невозможно, если бы вы могли получить 2 изменяемые ссылки на дочерние элементы одновременно, одну через ссылку self, а другую через прямые изменяемые ссылки на дочерние элементы внутри одного и того же Vec.

Итак, предположим, что у вас есть такое дерево:

let mut root = Dir {
    name: "root".into(),
    children: vec![],
};
let child = Dir {
    name: "child".into(),
    children: vec![],
};
root.children.push(child);
// assume this produces [&mut root, &mut root.children[0]]
let refs = root.flatten();
let child1 = &mut refs[0].children[0];
let child2 = refs[1];

child1 и child2 будут изменяемой ссылкой на одно и то же место в памяти, и это считается неопределенным поведением в Rust.

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


use std::{cell::RefCell, rc::Rc};
#[derive(Debug, Clone)]
struct Dir {
    name: String,
    children: Vec<Rc<RefCell<Dir>>>,
}

trait Flatten {
    fn flatten(self, flat: &mut Vec<Self>) where Self: Sized;
}
impl Flatten for Rc<RefCell<Dir>> {
    fn flatten(self, flat: &mut Vec<Self>) {
        flat.push(Rc::clone(&self));
        for child in &self.borrow().children {
            Rc::clone(child).flatten(flat);
        }
    }
}

impl Dir {
    fn new(name: impl Into<String>) -> Rc<RefCell<Self>> {
        Rc::new(RefCell::new(Dir {
            name: name.into(),
            children: Vec::new(),
        }))
    }
}

fn main() {
    let root = Dir::new("root");
    let child = Dir::new("child");
    let sibling = Dir::new("sibling");
    let grandchild = Dir::new("grandchild");
    child.borrow_mut().children.push(grandchild);
    root.borrow_mut().children.extend([child, sibling]);
    let mut refs = Vec::with_capacity(4);
    root.flatten(&mut refs);
    dbg!(refs);
}

Было бы возможно, если бы вы изменили объявление children на Vec<Rc<RefCell<Node>>>, чтобы вернуть плоский вексель?

Brendon 09.04.2023 20:26

Не Vec<&mut ...>, а какой-то плоский вектор, да.

cafce25 09.04.2023 20:28

Я хотел бы расширить ответ @ cafce25: вы не можете сгладить до Vec<&mut Dir>, потому что это даст вам два способа получить изменяемую ссылку на одно и то же. Один из этих путей проходит через children. Следовательно: вы все еще можете получить Vec<&mut …>, просто он не может включать доступ к children.

Итак, если у вас, например,

#[derive(Debug, Clone)]
struct NodeCommon {
    name: String,
    …
}

#[derive(Debug, Clone)]
struct Dir {
    common: NodeCommon,
    children: Vec<Node>,
}

вы можете получить Vec<&mut NodeCommon> с помощью кода выше. Детская площадка

Хотя я хотел бы спросить: что вы будете делать с этим Vec? Если вы собираетесь перебирать его только для изменения некоторых вещей, было бы несколько расточительно создавать для этого Vec. Более хорошим решением проблемы «абстрактного хождения по дереву» может быть вызов FnMut для каждого Dir:

impl Node {
    fn for_each_dir_mut<'a>(&'a mut self, f: &mut impl FnMut(&mut Dir)) {
        if let Node::Dir(dir) = self {
            f(dir);
            for node in dir.children.iter_mut() {
                node.for_each_dir_mut(f);
            }
        }
    }
}

Детская площадка

Может, я сейчас туплю. Как вы передаете это в рекурсивном вызове? ([Редактировать:] Удален вопрос, почему это &mut FnMut, а не просто FnMut.)

Caesar 10.04.2023 09:44

Ты прав, вот почему.

cafce25 10.04.2023 09:47

Вы правы. Если это внешний API, вероятно, следует предоставить функцию-оболочку.

Caesar 10.04.2023 09:51

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