Search code examples
rustreferenceownership

Take Ownership from retain or reference


I am writing a Rust parser toy, and I want to transform some nodes, there are three possible for this transform remove, keep, ToTopLevel, remove and keep is ok, but when I encounter ToTopLevel, I need to clone a node to push it into a temporary Vec, since it will delete node I wonder if there is any way I can get ownership and just give it to the temporary Vec.

use std::borrow::Cow;
#[derive(PartialEq, Clone)]
struct ASTNode<'a> {
    value: Cow<'a, str>,
    nodes: Vec<ASTNode<'a>>
}
struct Visitor<'a> {
    new_node: Vec<ASTNode<'a>>
}

enum Action {
    Remove,
    Keep,
    ToTopLevel,
}

impl<'a> Visitor<'a> {
    fn some_compute_to_determinate_action(&mut self, root: &mut ASTNode<'a>) -> Action {
        return Action::ToTopLevel;
    }
    fn visit(&mut self, root :&mut ASTNode<'a>) -> Action {
        let mut children_actions: Vec<Action> = Vec::new();
        for node in &mut root.nodes {
            children_actions.push(self.visit(node));
        }
        let mut index: usize = 0;
        root.nodes.retain(|node| {
            index+=1;
            match children_actions[index-1] {
                Action::Remove => false,
                Action::Keep => true,
                Action::ToTopLevel => {
                    // since it would delete node, is there are any way
                    // i can take ownership and push it to new_node vec ?
                    // like : self.new_node.push(*node);
                    self.new_node.push(node.clone());
                    return false;
                }
            }
        });
        return self.some_compute_to_determinate_action(root);
    }
    fn push_node_to_top_level_after_visit(&mut self,root: &mut ASTNode<'a>) {
        root.nodes.append(&mut self.new_node);
    }
}
fn main() {
    let mut root = ASTNode {
        value: Cow::Borrowed("root_value"),
        nodes: Vec::new()/*  assume there are a a lot of nested children  */
    };
    let mut visitor = Visitor {
        new_node: Vec::new()
    };
    visitor.visit(&mut root);
    visitor.push_node_to_top_level_after_visit(&mut root);
}

Solution

  • There is no iterator or method which hands off the item itself (it'd need something like T -> Option<T> as a callback, not sure why there isn't such a variant for drain and retain though, seems useful).

    However since it's possible to create an ASTNode cheaply:

    ASTNode {
        value: Cow::Borrowed(""),
        nodes: Vec::new()
    }
    

    what you can do is use Vec::retain_mut and std::mem::replace to take the "actual" ASTNode out replacing it with a dummy which you then don't keep:

        root.nodes.retain_mut(|node| {
            index+=1;
            match children_actions[index-1] {
                Action::Remove => false,
                Action::Keep => true,
                Action::ToTopLevel => {
                    self.new_node.push(std::mem::replace(node, ASTNode {
                        value: Cow::Borrowed(""),
                        nodes: Vec::new(),
                    }));
                    false
                }
            }
        });
    

    or if you derive(Default) on ASTNode, just std::mem::take

        root.nodes.retain_mut(|node| {
            index+=1;
            match children_actions[index-1] {
                Action::Remove => false,
                Action::Keep => true,
                Action::ToTopLevel => {
                    self.new_node.push(std::mem::take(node));
                    false
                }
            }
        });