是否可以在Rust中进行递归闭包?

时间:2013-06-05 18:05:58

标签: recursion closures rust

这是一个非常简单的例子,但我将如何做类似的事情:

let fact = |x: u32| {
    match x {
        0 => 1,
        _ => x * fact(x - 1),
    }
};

我知道这个具体的例子可以通过迭代轻松完成,但是我想知道是否可以在Rust中为更复杂的事情(例如遍历树)创建一个递归函数,或者我是否需要使用我的反而拥有堆栈。

2 个答案:

答案 0 :(得分:25)

有几种方法可以做到这一点。

您可以将闭包放入结构中并将此结构传递给闭包。您甚至可以在函数中内联定义结构:

fn main() {
    struct Fact<'s> { f: &'s Fn(&Fact, u32) -> u32 }
    let fact = Fact {
        f: &|fact, x| if x == 0 {1} else {x * (fact.f)(fact, x - 1)}
    };

    println!("{}", (fact.f)(&fact, 5));
}

这解决了有一个无限类型(一个将自己作为参数的函数)的问题,以及当一个人写fact时,let fact = |x| {...}尚未在闭包本身内定义的问题。所以不能在那里提到它。

这适用于Rust 1.17,但未来可能会被视为非法,因为它在某些情况下是危险的,如the blog post The Case of the Recurring Closure中所述。这里完全安全,因为没有突变。


另一种选择是将递归函数写为fn项,也可以在函数中内联定义:

fn main() {
    fn fact(x: u32) -> u32 { if x == 0 {1} else {x * fact(x - 1)} }

    println!("{}", fact(5));
}

如果您不需要从环境中捕获任何内容,这可以正常工作。


还有一个选择是使用fn项解决方案,但显式传递所需的args / environment。

fn main() {
    struct FactEnv { base_case: u32 }
    fn fact(env: &FactEnv, x: u32) -> u32 {
        if x == 0 {env.base_case} else {x * fact(env, x - 1)}
    }

    let env =  FactEnv { base_case: 1 };
    println!("{}", fact(&env, 5));
}

所有这些都适用于Rust 1.17并且可能在0.6版本之后起作用。 fn内部定义的fn与顶级定义的fn没有区别,除非它们只能在内部定义的{{1}}内访问。

答案 1 :(得分:0)

这是我想到的一个非常丑陋且冗长的解决方案:

use std::cell::RefCell;
use std::rc::Rc;
use std::rc::Weak;

fn main() {
    let weak_holder: Rc<RefCell<Weak<Fn(u32) -> u32>>> =
        Rc::new(RefCell::new(Weak::<fn(u32) -> u32>::new()));
    let weak_holder2 = weak_holder.clone();
    let fact: Rc<Fn(u32) -> u32> = Rc::new(move|x| {
        let fact = weak_holder2.borrow().upgrade().unwrap();
        return if x == 0 {1} else {x * fact(x - 1)};
    });
    weak_holder.replace(Rc::downgrade(&fact));

    println!("{}", fact(5)); // prints "120"
    println!("{}", fact(6)); // prints "720"
}

这样做的好处是,您可以使用预期的签名来调用函数(不需要额外的参数),它是一个闭包,可以捕获变量(通过移动),不需要定义任何新结构,并且闭包可以从函数返回或以其他方式存储在超出其创建范围的地方(作为Rc<Fn...>),并且仍然有效。