У меня есть древовидная структура в 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
, но мало о них знаю.
Это невозможно, если бы вы могли получить 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);
}
Не Vec<&mut ...>
, а какой-то плоский вектор, да.
Я хотел бы расширить ответ @ 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
.)
Ты прав, вот почему.
Вы правы. Если это внешний API, вероятно, следует предоставить функцию-оболочку.
Было бы возможно, если бы вы изменили объявление
children
наVec<Rc<RefCell<Node>>>
, чтобы вернуть плоский вексель?