rust 如何将闭包作为参数递归传递?

xvw2m8pv  于 2023-05-22  发布在  其他
关注(0)|答案(1)|浏览(198)

问题

我正在构建一个n-树的前序遍历,以这种方式为遍历中的每个节点执行一个闭包。
我的第一个方法是:

#[derive(Debug)]
pub struct Node<T> {
    value: T,
    children: Vec<Node<T>>,
}

impl<T> Node<T> {
    pub fn preorder<F>(&self, mut f: F)
    where
        F: FnMut(&Self),
    {
        f(self);
        self.children.iter().for_each(|child| child.preorder(f));
    }
}

但是,当在foreach迭代器中调用child.preorder(f)语句时,我遇到了一个错误。

error[E0507]: cannot move out of `f`, a captured variable in an `FnMut` closure
  --> src/lib.rs:12:62
   |
7  |     pub fn preorder<F>(&self, mut f: F)
   |                               ----- captured outer variable
...
12 |         self.children.iter().for_each(|child| child.preorder(f));
   |                                       -------                ^ move occurs because `f` has type `F`, which does not implement the `Copy` trait
   |                                       |
   |                                       captured by this `FnMut` closure

Playground

我找到的解决方案

将pre-order方法的头部更改为以下内容可以完美地编译,但它在要使用的闭包上设置了一个Copy必需项,这是我根本不希望的。

pub fn preorder<F>(&self, mut f: F)
    where
        F: FnMut(&Self) + Copy,

我发现的另一种解决闭包上的Copy必要条件的方法是使用浸入方法,如下所示(see it in the playground):

pub struct Node<T> {
    value: T,
    children: Vec<Node<T>>,
}

impl<T> Node<T> {
    fn preorder_immersion<F>(&self, f: &mut F)
    where
        F: FnMut(&Self),
    {
        f(self);
        self.children
            .iter()
            .for_each(|child| child.preorder_immersion(f));
    }

    pub fn preorder_mut<F>(&self, mut f: F)
    where
        F: FnMut(&Self),
    {
        self.preorder_immersion(&mut f);
    }
}

说真的,看起来很痛苦。基本上是因为它意味着每个遍历实现有两个方法。

我想要的

在调用pre-order方法时,如果能够做这样的事情就太好了:

let mut result = Vec::new();
node.preorder(|n| result.push(*n.value()));

而我给出的最新解决方案效果很好。

我的问题:还有更优雅的方式吗?我错过什么了吗?或者可以使用沉浸式方法吗?

svmlkihl

svmlkihl1#

就像我在前面的评论中所说的,我会使用基于Iterator的方法,在探索过程中,我发现这个解决方案在我看来非常优雅:

impl<T> Node<T> {
    pub fn preorder(&self) -> impl Iterator<Item = &Self> {
        Iter::new(self)
    }
}

struct Iter<'a, T> {
    left: Vec<&'a Node<T>>,
}

impl<'a, T> Iter<'a, T> {
    fn new(root: &'a Node<T>) -> Self {
        Self { left: vec![root] }
    }
}

impl<'a, T> Iterator for Iter<'a, T> {
    type Item = &'a Node<T>;
    fn next(&mut self) -> Option<Self::Item> {
        let current = self.left.pop()?;
        self.left.extend(current.children.iter().rev());
        Some(current)
    }
}

我们将尚未产生的节点存储在Vec中,反过来,我们可以使用popextend,这应该是非常有效的。每当我们生成一个Node时,我们将它的子节点添加到剩余的节点中。
而不是像node.preorder(your_closure_or_function)这样使用它,你可以像node.preorder().for_each(your_closure_or_function)一样使用它,但是为了一个额外的函数调用的小代价,你可以获得Rust中已经围绕迭代器构建的整个功能和工具集。

相关问题