Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to make a recursive closure in Rust?

This is a very simple example, but how would I do something similar to:

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

I know that this specific example can be easily done with iteration, but I'm wondering if it's possible to make a recursive function in Rust for more complicated things (such as traversing trees) or if I'm required to use my own stack instead.

like image 644
Undeterminant Avatar asked Jun 05 '13 18:06

Undeterminant


People also ask

Can you do recursion in Rust?

Recursion is possible in Rust, but it's not really encouraged. Instead, Rust favors something called iteration, also known as looping.

Does Rust optimize tail recursion?

There is only one problem: Rust does not do any tail call optimization (yet). Now, the crate tramp does its job. We implemented in this crate a trampoline. A trampoline simulates a tail call optimization by calling a function which might return another function (which will be called again) or a computed result.

Can recursion stop?

In a recursive function, there must be a termination condition that allows the function to exit without calling itself. This condition allows the recursion to stop - that is, without it the function would call itself over and over again, resulting in an error.

How do you stop recursion at a certain point?

Pass in a "recursion depth" parameter to your recursive function, incrementing it for every call to the function. When you hit your limit, you stop recursing.


2 Answers

There are a few ways to do this.

You can put closures into a struct and pass this struct to the closure. You can even define structs inline in a function:

fn main() {     struct Fact<'s> { f: &'s dyn 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)); } 

This gets around the problem of having an infinite type (a function that takes itself as an argument) and the problem that fact isn't yet defined inside the closure itself when one writes let fact = |x| {...} and so one can't refer to it there.


Another option is to just write a recursive function as a fn item, which can also be defined inline in a function:

fn main() {     fn fact(x: u32) -> u32 { if x == 0 {1} else {x * fact(x - 1)} }      println!("{}", fact(5)); } 

This works fine if you don't need to capture anything from the environment.


One more option is to use the fn item solution but explicitly pass the args/environment you want.

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)); } 

All of these work with Rust 1.17 and have probably worked since version 0.6. The fn's defined inside fns are no different to those defined at the top level, except they are only accessible within the fn they are defined inside.

like image 148
huon Avatar answered Sep 20 '22 19:09

huon


Here's a really ugly and verbose solution I came up with:

use std::{     cell::RefCell,     rc::{Rc, Weak}, };  fn main() {     let weak_holder: Rc<RefCell<Weak<dyn Fn(u32) -> u32>>> =         Rc::new(RefCell::new(Weak::<fn(u32) -> u32>::new()));     let weak_holder2 = weak_holder.clone();     let fact: Rc<dyn Fn(u32) -> u32> = Rc::new(move |x| {         let fact = weak_holder2.borrow().upgrade().unwrap();         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" } 

The advantages of this are that you call the function with the expected signature (no extra arguments needed), it's a closure that can capture variables (by move), it doesn't require defining any new structs, and the closure can be returned from the function or otherwise stored in a place that outlives the scope where it was created (as an Rc<Fn...>) and it still works.

like image 35
newacct Avatar answered Sep 22 '22 19:09

newacct